Понадобилось мне как-то вычитывать строчки из файла и создавать на основе этих строк объекты 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.txt
add_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».
Удалить