Фабрики и бибилотеки

NB: скучный и технический пост о землях Страуструповых.

Потребовалось засунуть фабрику (см. GoF [1]) в статическую библиотеку и это породило удивительные эффекты, о которых по крайней мере я ранее не знал.

Итак, имеем фабрику в следующем виде (ее устройство не обсуждаем, несмотря на то, что она самописная, ее структура стандартная с небольшими плюшками в виде параметризуемого набора аргументов конструктора, смотри Александреску [2]):

Factory.h

template<typename Base, typename Args>
class NewCreatorBase
{
public:
    virtual Base * operator()(Args args) = 0;
    virtual ~NewCreatorBase(){}
};


template<typename Base, typename Derived, typename Args >
class NewCreatorImpl : public NewCreatorBase<Base, Args>
{
public:
    Derived * operator()(Args args) override
    {
        return new Derived(args);
    }
};

template<typename Base, typename Args>
class NewCreatorHolder
{
    std::unique_ptr<NewCreatorBase<Base, Args> > _pimpl; // NOT actual _pimpl!!!
public:
    template<typename Derived>
    explicit NewCreatorHolder(boost::mpl::identity<Derived>  )
    {
        _pimpl.reset(new NewCreatorImpl<Base, Derived, Args> );
    }

    Base * operator()(Args args)
    {
        return _pimpl->operator()( args );
    }
};

template<typename Product, typename Identifier, typename Creator = NewCreatorHolder<Product> >
class Factory
{
    typedef Factory<Product, Identifier,Creator> ThisType;

    std::map<Identifier,Creator> _creators;

public:

    typedef Identifier    IdentifierType;
    typedef Product        ProductType;
    typedef Creator        CreatorType;

    int register_type( Identifier id, Creator creator )
    {
        auto r = _creators.insert( std::make_pair(id, std::move(creator) ) );

        if( ! r.second )
        {
            std::ostringstream ost;
            ost << "Factory::register_type: id: " << id << " already registered";
        
            throw std::logic_error( ost.str() );
        }

        return 1;
    }


    static ThisType * instance()
    {
        static ThisType static_instance;
    
        return &static_instance;
    }


    bool find(Identifier id)
    {
        return _creators.count(id) > 0;
    }

    std::vector<Identifier> list_all_registered_id() const
    {
        std::vector<Identifier> ret;
    
        for( auto & x : _creators )
        {
            ret.push_back( x.first );
        }
    
        return ret;
    }

    template<typename Args>
    std::unique_ptr<Product> create(Identifier id, Args args)
    {
        auto it = _creators.find( id );
        if( it == _creators.end() )
        {
            std::ostringstream ost;
            ost << "Factory::create: id: " << id << " has not been registered";

            throw std::runtime_error( ost.str() );
        }
    
        return std::unique_ptr<Product>( it->second(args) );
    }


};

template<typename ConcreteProduct, typename Factory>
class FactoryRegistrar
{
public:
    FactoryRegistrar(const typename Factory::IdentifierType & id )
    {
        // std::cout << "FactoryRegistrar: " << __PRETTY_FUNCTION__ << std::endl;
        typedef typename Factory::CreatorType T;
        boost::mpl::identity<ConcreteProduct> tag;
        T creator ( tag );
        Factory::instance()->register_type(id, std::move(creator) );
    }
    
    int get()
    {
        return 1;
    }
    
};

Используется фабрика следующим образом. Допустим, нам необходимо создавать классы производные от `Base` c помощью фабрики по строковому идентификатору.

Определяем класс `Base` и соответствующую фабрику.

Base.h

class Base
	{
	public:
		Base();
		virtual std::string identify_me() = 0;
	};

	typedef Factory<Base,std::string> BaseFactory;

Для регистрации класса `Derived` производного от `Base` в фабрике в Derived.cpp файле необходимо определить глобально и разумнее всего статически

Derided.cpp  

static FactoryRegistrar<Derived,BaseFactory> __factory_registrar__;

конструктор которого зарегистрирует `Derived` в фабрике. Для включения класса в фабрику теперь достаточно просто включить файл Derived.cpp в сборку (скомпилировать и передать линковщику соответствующий объектный файл). Все кул, что хотели – то и получили!

Теперь пытаемся вынести все это в статическую бибилотеку, которая предоставляет следующий интерфейс: – определение класса `Base` – определение фабрики для класса `Base`, в которой зарегистрированно сколько-то конкретных производных классов в зависиости от нашей прихоти (с какими опциями собиралсь бибилотека)

Пользователь библиотеки хочет просто создать сущность с помощью `BaseFactory::instance()->create(“Dervied”)`. Если такой класс не был зарегистрирован в библиотеке, то вернется нулевой указатель, иначе указатель на созданный объект.

Проблема заключается в том, что если засунуть все в статическую бибилотеку и собрать проект с ней, то фабрика всегда будет возвращать ноль. Это было неприятным сюрпризом, ноги которого растут из правил линковки.

Если передать линковщику объектный файл напрямую, то он обязан включить его в исполняемый файл и, соотвественно, проинициализировать все переменные с статической областью видимости. В случае бибилиотеки, являющейся не более чем собранием объекнтых файлов, линковщик обращается к ней в том и только в том случае, если не может разрешить какую-то ссылку. В этом случае он включает объектный код, соответствующий этой функции и объектный код, ответственный за инициализацию объектов с статической областью видимости. Так как в нашем случае нет неразрешенных ссылок на объектный файл `Derived.cpp.o`, то и не происходит инициализации `FactoryRegistrar` и регистрации в фабрике.

Для решения этой проблемы необходимо создавать ссылки на объектный файл, путем вызова функции, скажем, `Derived::dummy()` определенной в `Derived.cpp.o`, чего и хотелось избежать: зависимости фабрики от регистрируемых в ней классов.

Мораль: не помещать фабрики с статические библиотеки.

Библиография:

1. Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес Приемы объектно-ориентированного проектирования. Паттерны проектирования = Design Patterns: Elements of Reusable Object-Oriented Software. — СПб: «Питер», 2007. — С. 366. — ISBN 978-5-469-01136-1

2. Александреску А. Современное проектирование на С++: Обобщенное программирование и прикладные шаблоны проектирования = Modern C++ Design: Generic Programming and Design Patterns Applied. — С. П.: Вильямс, 2008. — 336 с. — (С++ in Depth). — ISBN 978-5-8459-0351-8

P.S. Мой первый пост на re-coders на техническую тему и что я обнружваю? - не работает, использованное <pre> - кое-как, съедает “эллипсисы” (…), блок для кодеров который не может нормально отображать код - пичалька…

blog comments powered by Disqus