Понадобилось мне как-то вычитывать строчки из файла и создавать на основе этих строк объекты C++. Решается эта задача фабриками объектов.
Сел и написал свою простую фабрику. Может недостаточно гибкую, но с моей задачей справляется «на ура». Вот она (factory.h):
Ну и простой пример её использования.
fruitfactory.h:
fruitbase.h
apple.h
apple.cpp
И наконец main.cpp
Если все это просто взять и скомпилировать, то программа работает как ожидается. Яблоко успешно регистрируется в фабрике фруктов и к завершению программы оказывается съеденным. :)
Но если хочется собрать из фруктов и фабрики статическую библиотеку и отдельно прилинковать её к приложению, то ничего не выйдет.
CMakeLists.txt
Похоже, что линковщик не прилинковывает реализацию apple.cpp. Что не удивительно, т.к. класс Apple нигде в программе явно не используется. А вот ведь нужна статическая линковка. Как быть?
UPD:
Решение
Решилась проблема опцией линковщику. Правильный CMakeLists.txt выглядит так:
Опция -Wl,-whole-archive заставляет ld прилинковать библиотеку целиком. А последующая -Wl,-no-whole-archive отменяет включенное поведение. Т.е. целиком вставится только одна библиотека фруктов. :)
http://stackoverflow.com/questions/5693405/specifying-link-flags-for-only-one-static-lib-while-linking-executable
Сел и написал свою простую фабрику. Может недостаточно гибкую, но с моей задачей справляется «на ура». Вот она (factory.h):
#ifndef FACTORY_H
#define FACTORY_H
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <map>
#include <string>
template <class T, class D>
class Factory : private boost::noncopyable
{
public:
typedef boost::shared_ptr<T> Product;
typedef Product (*CallBack) (D);
typedef std::map<std::string, CallBack> FactoryCont;
private:
static Factory<T, D>* _instance;
static boost::mutex _guard;
FactoryCont _factories;
Factory () {};
public:
static Factory<T, D>* get_instance ()
{
if (! _instance) {
boost::mutex::scoped_lock l(_guard);
if (! _instance) _instance = new Factory<T, D>;
}
return _instance;
}
bool reg (const std::string& name, CallBack lathe)
{
return _factories.insert(
typename FactoryCont::value_type(name, lathe)).second;
}
Product create (const std::string& name, D data) const
{
typename FactoryCont::const_iterator p = _factories.find(name);
if (p == _factories.end())
throw std::runtime_error(
"can't find factory for '" + name + "'");
return (p->second)(data);
}
};
template<class T, class D>
Factory<T, D>* Factory<T, D>::_instance = NULL;
template<class T, class D>
boost::mutex Factory<T, D>::_guard;
#endif /* FACTORY_H */
Ну и простой пример её использования.
fruitfactory.h:
#ifndef FRUITFACTORY_H #define FRUITFACTORY_H #include <string> #include "factory.h" #include "fruitbase.h" typedef Factory<FruitBase, const std::string&> FruitFactory; #endif /* FRUITFACTORY_H */
fruitbase.h
#ifndef FRUITBASE_H
#define FRUITBASE_H
class FruitBase
{
private:
public:
virtual void eat () const = 0;
};
#endif /* FRUITBASE_H */
apple.h
#ifndef APPLE_H
#define APPLE_H
#include <iostream>
#include "fruitbase.h"
#include "fruitfactory.h"
class Apple : public FruitBase
{
private:
public:
virtual void eat () const;
};
#endif /* APPLE_H */
apple.cpp
#include "apple.h"
namespace
{
FruitFactory::Product new_apple (const std::string& data)
{
return FruitFactory::Product(new Apple);
}
const bool registered =
FruitFactory::get_instance()->reg("apple", new_apple);
}
void Apple::eat () const
{
std::cout << "I eat an apple!\n";
}
И наконец main.cpp
#include "fruitfactory.h"
int main (int argc, char** argv)
{
FruitFactory::Product f =
FruitFactory::get_instance()->create("apple", "red");
f->eat();
return 0;
}
CMakeLists.txtadd_executable (main main.cpp apple.cpp apple.h factory.h fruitbase.h fruitfactory.h )
Если все это просто взять и скомпилировать, то программа работает как ожидается. Яблоко успешно регистрируется в фабрике фруктов и к завершению программы оказывается съеденным. :)
% ./main I eat an apple!
Но если хочется собрать из фруктов и фабрики статическую библиотеку и отдельно прилинковать её к приложению, то ничего не выйдет.
CMakeLists.txt
add_executable (main main.cpp ) target_link_libraries (main fruits) add_library (fruits STATIC apple.cpp apple.h factory.h fruitbase.h fruitfactory.h )Запуск:
% ./main terminate called after throwing an instance of 'std::runtime_error' what(): can't find factory for 'apple'
Похоже, что линковщик не прилинковывает реализацию apple.cpp. Что не удивительно, т.к. класс Apple нигде в программе явно не используется. А вот ведь нужна статическая линковка. Как быть?
UPD:
Решение
Решилась проблема опцией линковщику. Правильный CMakeLists.txt выглядит так:
add_executable (main
main.cpp
)
add_library (fruits STATIC
apple.cpp
apple.h
factory.h
fruitbase.h
fruitfactory.h
)
set (LIBFRUITS -Wl,-whole-archive fruits -Wl,-no-whole-archive)
target_link_libraries (main ${LIBFRUITS})
Опция -Wl,-whole-archive заставляет ld прилинковать библиотеку целиком. А последующая -Wl,-no-whole-archive отменяет включенное поведение. Т.е. целиком вставится только одна библиотека фруктов. :)
http://stackoverflow.com/questions/5693405/specifying-link-flags-for-only-one-static-lib-while-linking-executable
Как выход можно зарегить класс явно:
ОтветитьУдалитьв apple.h
FigureFactory::Product new_apple(const std::string& data);
#define CLASS_REGISTER(name, callback) \
FigureFactory::get_instance()->reg(#name, callback);
в apple.cpp
FigureFactory::Product
new_apple(const std::string& data)
{
return FigureFactory::Product(new Apple);
}
В main.cpp
CLASS_REGISTER(apple, new_apple)
FigureFactory::Product f =
FigureFactory::get_instance()->create("apple", "red");
f->foo();
Такое решение мне известно, но я его не рассматривал, потому что при этом усложняется добавление новых продуктов.
ОтветитьУдалитьЕсли использовать твой вариант, то при добавлении нового продукта придется в main.cpp подключать заголовочный файл, добавлять ещё один CLASS_REGISTER. Т.е. в проекте будет место, где надо перечислить все продукты.
А в реализации, приведённой в посте, каждый продукт регистрирует себя сам.
Конструктор и деструктор записать бы в приватную область.
ОтветитьУдалить@Анонимный
ОтветитьУдалитьВероятно, речь идёт о классе Factory? Конструктор спрятан, а вот деструктор забыл спрятать.
Да так лучше, если вы знакомы с реализацией variant то наверное лучше бы было тогда задачку решать таким образом (вы бы тогда хранили бы просто типы и при необходимости бы создавали бы новый объекты этого типа, а не заставляли бы разработчиков самому писать функцию callback). Это только мое мнение, но так наверное будет красивее
ОтветитьУдалитьЧестно говоря, не понял как тут можно применить variant (речь идет ведь об этом: http://insidecpp.ru/patterns/variant/). В C++ мы же не можем хранить типы в какой либо структуре данных, а потом их использовать, как это можно делать, например, в python.
ОтветитьУдалитьЧитайте, можно все, даже с этого же сайта взял ))
ОтветитьУдалитьhttp://insidecpp.ru/patterns/factory/
Теперь понятно. Это я видел, но мне не понравилось. :)
УдалитьЕсли хочется как либо инициализировать объект при создании, сделать проверки и т.п., то удобно иметь отдельную функцию, в которую это все вынесено.
И фабрика по ссылке не лишена проблемы, поднятой в этой заметке.
Согласен ))
ОтветитьУдалитьЕще хочется добавить, что если в коде на insidecpp захочется инициализировать объект при создании, то придется писать для классов продуктов специальный конструктор с параметром, а это почти тоже самое, что «писать функцию callback».
Удалить