Подтвердить что ты не робот

Элегантный способ реализации расширяемых фабрик в С++

Я ищу интуитивно понятный и расширяемый способ реализации фабрик для подклассов заданного базового класса в . Я хочу предоставить такую ​​функцию factory в библиотеке. Сложная часть заключается в том, что я хочу, чтобы упомянутый factory работал также для пользовательских подклассов (например, имея библиотеку factory), строит разные подклассы в зависимости от того, какие модули связаны с ним). Цель состоит в том, чтобы иметь минимальное бремя/путаницу для разработчиков, работающих ниже по течению, для использования заводов.

Пример того, что я хочу сделать, - это дать std::istream, построить и вернуть объект любого подкласса, соответствующего содержанию, или нулевой указатель, если совпадений не найдено. Глобальный factory будет иметь такую ​​подпись, как:

Base* Factory(std::istream &is){ ... };

Я знаком с прототипами фабрик, но я предпочитаю избегать необходимости создавать/хранить объекты-прототипы. Связанный вопрос размещен здесь для : Разрешить максимальную гибкость/расширяемость с помощью factory.

Я не ищу -специфические решения на данный момент, но если они более элегантны, я был бы рад узнать об этом.

Я придумал одно рабочее решение, которое, я считаю, довольно элегантно, и я отправлю его в качестве ответа. Я могу представить, что эта проблема довольно распространена, поэтому мне интересно, знает ли кто-нибудь о лучших подходах.

РЕДАКТИРОВАТЬ: кажется, что некоторые уточнения в порядке...

Идея состоит в том, чтобы factory построил объект производного класса, не содержащий логики, чтобы решить, какой из них. Хуже того, метод factory окажется частью библиотеки, а производные классы могут быть определены в плагинах.

Производные классы должны быть в состоянии сами решить, подходят ли они для построения, на основе предоставленного ввода (например, входного файла). Это решение может быть реализовано как предикат, который может использоваться factory, как было предложено несколькими людьми (большое предложение, кстати!).

4b9b3361

Ответ 1

Если я это правильно понимаю, нам нужна функция factory, которая может выбрать, какой производный класс должен создаваться на основе входных данных конструктора. Это наиболее общее решение, которое я мог бы предложить до сих пор. Вы указываете входы отображения для организации factory функций, а затем вы можете указать входные данные конструктора при вызове factory. Я не хочу сказать, что код объясняет больше, чем я мог, но я думаю, что примеры реализации FactoryGen.h в Base.h и Derived.h достаточно ясны с помощью комментариев. При необходимости я могу предоставить более подробную информацию.

FactoryGen.h

#pragma once

#include <map>
#include <tuple>
#include <typeinfo>

//C++11 typename aliasing, doesn't work in visual studio though...
/*
template<typename Base>
using FactoryGen<Base> = FactoryGen<Base,void>;
*/

//Assign unique ids to all classes within this map.  Better than typeid(class).hash_code() since there is no computation during run-time.
size_t __CLASS_UID = 0;

template<typename T>
inline size_t __GET_CLASS_UID(){
    static const size_t id = __CLASS_UID++;
    return id;
}

//These are the common code snippets from the factories and their specializations. 
template<typename Base>
struct FactoryGenCommon{
    typedef std::pair<void*,size_t> Factory; //A factory is a function pointer and its unique type identifier

    //Generates the function pointer type so that I don't have stupid looking typedefs everywhere
    template<typename... InArgs>
    struct FPInfo{ //stands for "Function Pointer Information"
        typedef Base* (*Type)(InArgs...);
    };

    //Check to see if a Factory is not null and matches it signature (helps make sure a factory actually takes the specified inputs)
    template<typename... InArgs>
    static bool isValid(const Factory& factory){
        auto maker = factory.first;
        if(maker==nullptr) return false;

        //we have to check if the Factory will take those inArgs
        auto type = factory.second;
        auto intype = __GET_CLASS_UID<FPInfo<InArgs...>>();
        if(intype != type) return false;

        return true;
    }
};

//template inputs are the Base type for which the factory returns, and the Args... that will determine how the function pointers are indexed.
template<typename Base, typename... Args> 
struct FactoryGen : FactoryGenCommon<Base>{
    typedef std::tuple<Args...> Tuple;
    typedef std::map<Tuple,Factory> Map; //the Args... are keys to a map of function pointers

    inline static Map& get(){ 
        static Map factoryMap;
        return factoryMap; 
    }

    template<typename... InArgs>
    static void add(void* factory, const Args&... args){
        Tuple selTuple = std::make_tuple(args...); //selTuple means Selecting Tuple.  This Tuple is the key to the map that gives us a function pointer
        get()[selTuple] = Factory(factory,__GET_CLASS_UID<FPInfo<InArgs...>>());
    }

    template<typename... InArgs>
    static Base* make(const Args&... args, const InArgs&... inArgs){
        Factory factory = get()[std::make_tuple(args...)];
        if(!isValid<InArgs...>(factory)) return nullptr;
        return ((FPInfo<InArgs...>::Type)factory.first) (inArgs...);
    }
};

//Specialize for factories with no selection mapping
template<typename Base>
struct FactoryGen<Base,void> : FactoryGenCommon<Base>{
    inline static Factory& get(){
        static Factory factory;
        return factory; 
    }

    template<typename... InArgs>
    static void add(void* factory){
        get() = Factory(factory,__GET_CLASS_UID<FPInfo<InArgs...>>());
    }

    template<typename... InArgs>
    static Base* make(const InArgs&... inArgs){
        Factory factory = get();
        if(!isValid<InArgs...>(factory)) return nullptr;
        return ((FPInfo<InArgs...>::Type)factory.first) (inArgs...);
    }
};

//this calls the function "initialize()" function to register each class ONCE with the respective factory (even if a class tries to initialize multiple times)
//this step can probably be circumvented, but I'm not totally sure how
template <class T>
class RegisterInit {
  int& count(void) { static int x = 0; return x; } //counts the number of callers per derived
public:
  RegisterInit(void) { 
    if ((count())++ == 0) { //only initialize on the first caller of that class T
      T::initialize();
    }
  }
};

Base.h

#pragma once

#include <map>
#include <string>
#include <iostream>
#include "Procedure.h"
#include "FactoryGen.h"

class Base {
public:
    static Base* makeBase(){ return new Base; }
    static void initialize(){ FactoryGen<Base,void>::add(Base::makeBase); } //we want this to be the default mapping, specify that it takes void inputs

    virtual void speak(){ std::cout << "Base" << std::endl; }
};

RegisterInit<Base> __Base; //calls initialize for Base

Derived.h

#pragma once

#include "Base.h"

class Derived0 : public Base {
private:
    std::string speakStr;
public:
    Derived0(std::string sayThis){ speakStr=sayThis; }

    static Base* make(std::string sayThis){ return new Derived0(sayThis); }
    static void initialize(){ FactoryGen<Base,int>::add<std::string>(Derived0::make,0); } //we map to this subclass via int with 0, but specify that it takes a string input

    virtual void speak(){ std::cout << speakStr << std::endl; }
};

RegisterInit<Derived0> __d0init; //calls initialize() for Derived0

class Derived1 : public Base {
private:
    std::string speakStr;
public:
    Derived1(std::string sayThis){ speakStr=sayThis; }

    static Base* make(std::string sayThat){ return new Derived0(sayThat); }
    static void initialize(){ FactoryGen<Base,int>::add<std::string>(Derived0::make,1); } //we map to this subclass via int with 1, but specify that it takes a string input

    virtual void speak(){ std::cout << speakStr << std::endl; }
};

RegisterInit<Derived1> __d1init; //calls initialize() for Derived1

main.cpp

#include <windows.h> //for Sleep()
#include "Base.h"
#include "Derived.h"

using namespace std;

int main(){
    Base* b = FactoryGen<Base,void>::make(); //no mapping, no inputs
    Base* d0 = FactoryGen<Base,int>::make<string>(0,"Derived0"); //int mapping, string input
    Base* d1 = FactoryGen<Base,int>::make<string>(1,"I am Derived1"); //int mapping, string input

    b->speak();
    d0->speak();
    d1->speak();

    cout << "Size of Base: " << sizeof(Base) << endl;
    cout << "Size of Derived0: " << sizeof(Derived0) << endl;

    Sleep(3000); //Windows & Visual Studio, sry
}

Я думаю, что это довольно гибкая/расширяемая библиотека factory. Хотя код для него не очень интуитивный, я думаю, что использование его довольно просто. Конечно, мой взгляд предвзятый, потому что я тот, кто его написал, поэтому, пожалуйста, дайте мне знать, если это наоборот.

РЕДАКТИРОВАТЬ: Очистить файл FactoryGen.h. Вероятно, это последнее обновление, но это было веселое упражнение.

Ответ 2

Мои комментарии были, вероятно, не очень ясными. Итак, вот С++ 11 "решение", опираясь на мета-программирование шаблонов: (Возможно, это не самый приятный способ сделать это)

#include <iostream>
#include <utility>


// Type list stuff: (perhaps use an existing library here)
class EmptyType {};

template<class T1, class T2 = EmptyType>
struct TypeList
{
    typedef T1 Head;
    typedef T2 Tail;
};

template<class... Etc>
struct MakeTypeList;

template <class Head>
struct MakeTypeList<Head>
{
    typedef TypeList<Head> Type;
};

template <class Head, class... Etc>
struct MakeTypeList<Head, Etc...>
{
    typedef TypeList<Head, typename MakeTypeList<Etc...>::Type > Type;
};

// Calling produce
template<class TList, class BaseType>
struct Producer;

template<class BaseType>
struct Producer<EmptyType, BaseType>
{
    template<class... Args>
    static BaseType* Produce(Args... args)
    {
        return nullptr;
    }
};

template<class Head, class Tail, class BaseType>
struct Producer<TypeList<Head, Tail>, BaseType>
{
    template<class... Args>
    static BaseType* Produce(Args... args)
    {
        BaseType* b = Head::Produce(args...);
        if(b != nullptr)
            return b;
        return Producer<Tail, BaseType>::Produce(args...);
    }
};

// Generic AbstractFactory:
template<class BaseType, class Types>
struct AbstractFactory {
    typedef Producer<Types, BaseType> ProducerType;

    template<class... Args>
    static BaseType* Produce(Args... args)
    {
        return ProducerType::Produce(args...);
    }
};

class Base {}; // Example base class you had

struct Derived0 : public Base { // Example derived class you had
    Derived0() = default;
    static Base* Produce(int value)
    {
        if(value == 0)
            return new Derived0();
        return nullptr;
    }
};

struct Derived1 : public Base { // Another example class
    Derived1() = default;
    static Base* Produce(int value)
    {
        if(value == 1)
            return new Derived1();
        return nullptr;
    }
};

int main()
{
    // This will be our abstract factory type:
    typedef AbstractFactory<Base, MakeTypeList<Derived0, Derived1>::Type> Factory;
    Base* b1 = Factory::Produce(1);
    Base* b0 = Factory::Produce(0);
    Base* b2 = Factory::Produce(2);
    // As expected b2 is nullptr
    std::cout << b0 << ", " << b1 << ", " << b2 << std::endl;
}

Преимущества:

  • Нет (дополнительных) служебных данных во время выполнения, как и у указателей функций. Работает для любого базового типа и для любого количества производных типов. Вы все же в конечном итоге вызываете функции, конечно.
  • Благодаря вариационным шаблонам это работает с любым количеством аргументов (при некорректном числе аргументов будет выдано сообщение об ошибке времени компиляции).
  • Явная регистрация функций-членов продукта   не требуется.

Недостатки:

  • Все ваши производные типы должны быть доступны, когда вы объявляете Factory. (Вы должны знать, что такое возможные производные типы, и они должны быть завершены.)
  • Функции-члены продукта для производных типов должны быть общедоступными.
  • Может сделать компиляцию медленнее. (Как всегда, когда вы полагаетесь на метапрограммирование шаблона)

В конце концов, использование шаблона проектирования прототипа может получиться лучше. Я не знаю, так как я не пробовал использовать свой код.

Я хотел бы указать некоторые дополнительные вещи (после дальнейшего обсуждения чата):

  • Каждый factory может возвращать только один объект. Это кажется странным, так как пользователи решают, будут ли они принимать входные данные для создания своего объекта или нет. Я бы по этой причине предположил, что ваш factory может возвращать коллекцию объектов вместо этого.
  • Будьте осторожны, чтобы не перегружать вещи. Вы хотите систему плагинов, но я не думаю, что вы действительно хотите фабрики. Я предлагаю вам просто сделать, чтобы пользователи регистрировали свои классы (в их общем объекте) и просто передавали аргументы классам Produce (статические) функции-члены. Вы храните объекты тогда и только тогда, когда они не являются nullptr.

Ответ 3

Обновление. Этот ответ сделал предположение, что существует какой-то magic, который можно прочитать и передать к factory, но, по-видимому, это не так. Я оставляю ответ здесь, потому что: а) я могу обновить его, и б) Мне все равно нравится.


Не сильно отличается от вашего собственного ответа, не используя методы С++ 11 (у меня еще не было возможности его обновить или вернуть интеллектуальный указатель и т.д.), а не полностью моя собственная работа, но это класс factory, который я использую. Важно (IMHO), он не вызывает каждый метод класса, чтобы найти тот, который соответствует - он делает это через карту.

#include <map>
// extraneous code has been removed, such as empty constructors, ...
template <typename _Key, typename _Base, typename _Pred = std::less<_Key> >
class Factory {
public:
    typedef _Base* (*CreatorFunction) (void);
    typedef std::map<_Key, CreatorFunction, _Pred> _mapFactory;

    // called statically by all classes that can be created
    static _Key Register(_Key idKey, CreatorFunction classCreator) {
        get_mapFactory()->insert(std::pair<_Key, CreatorFunction>(idKey, classCreator));
        return idKey;
    }

    // Tries to create instance based on the key
    static _Base* Create(_Key idKey) {
        _mapFactory::iterator it = get_mapFactory()->find(idKey);
        if (it != get_mapFactory()->end()) {
            if (it->second) {
                return it->second();
            }
        }
        return 0;
    }

protected:
    static _mapFactory * get_mapFactory() {
        static _mapFactory m_sMapFactory;
        return &m_sMapFactory;
    }
};

Чтобы использовать это, вы просто объявляете базовый тип, и для каждого класса вы регистрируете его как статический. Обратите внимание, что когда вы регистрируетесь, ключ возвращается, поэтому я склонен добавлять это как член класса, но это необязательно, просто аккуратно:)...

// shape.h
// extraneous code has been removed, such as empty constructors, ...
// we also don't technically need the id() method, but it could be handy
// if at a later point you wish to query the type.
class Shape {
public:
    virtual std::string id() const = 0;
};
typedef Factory<std::string, Shape> TShapeFactory;

Теперь мы можем создать новый производный класс и зарегистрировать его как создаваемый с помощью TShapeFactory...

// cube.h
// extraneous code has been removed, such as empty constructors, ...
class Cube : public Shape {
protected:
    static const std::string _id;
public:
    static Shape* Create() {return new Cube;}
    virtual std::string id() const {return _id;};
};

// cube.cpp
const std::string Cube::_id = TShapeFactory::Register("cube", Cube::Create);

Затем мы можем создать новый элемент на основе, в данном случае, строки:

Shape* a_cube = TShapeFactory::Create("cube");
Shape* a_triangle = TShapeFactory::Create("triangle");
// a_triangle is a null pointer, as we've not registered a "triangle"

Преимущество этого метода заключается в том, что если вы создаете новый производный класс factory -generatable, вам не нужно менять какой-либо другой код, если вы можете увидеть класс factory и получить из базы:

// sphere.h
// extraneous code has been removed, such as empty constructors, ...
class Sphere : public Shape {
protected:
    static const std::string _id;
public:
    static Shape* Create() {return new Sphere;}
    virtual std::string id() const {return _id;};
};

// sphere.cpp
const std::string Sphere::_id = TShapeFactory::Register("sphere", Sphere::Create);

Возможные улучшения, которые я оставляю читателю, включают добавление таких вещей, как: typedef _Base base_class to Factory, так что, когда вы объявили свой пользовательский factory, вы можете сделать свои классы из TShapeFactory::base_class, и так далее. factory должен, вероятно, также проверить, существует ли ключ, но снова... он оставлен как упражнение.

Ответ 4

Лучшее решение, о котором я могу сейчас думать, - это использовать класс Factory, который хранит указатели для создания функций для каждого производного класса. Когда создается новый производный класс, указатель функции на метод создания может быть сохранен в factory.

Вот какой код для иллюстрации моего подхода:

#include <iostream>
#include <vector>

class Base{};

// Factory class to produce Base* objects from an int (for simplicity).
// The class uses a list of registered function pointers, which attempt
// to produce a derived class based on the given int.
class Factory{
public:
    typedef Base*(*ReadFunPtr)(int);
private:
    static vector<ReadFunPtr> registeredFuns;
public:
    static void registerPtr(ReadFunPtr ptr){ registeredFuns.push_back(ptr); }
    static Base* Produce(int value){
        Base *ptr=NULL;
        for(vector<ReadFunPtr>::const_iterator I=registeredFuns.begin(),E=registeredFuns.end();I!=E;++I){
            ptr=(*I)(value);
            if(ptr!=NULL){
                return ptr;
            }
        }
        return NULL;
    }
};
// initialize vector of funptrs
std::vector<Factory::ReadFunPtr> Factory::registeredFuns=std::vector<Factory::ReadFunPtr>();

// An example Derived class, which can be produced from an int=0. 
// The producing method is static to avoid the need for prototype objects.
class Derived : public Base{
    private:
        static Base* ProduceDerivedFromInt(int value){ 
            if(value==0) return new Derived();
            return NULL;
        }
public:
    Derived(){};

    // registrar is a friend because we made the producing function private
    // this is not necessary, may be desirable (e.g. encapsulation)
    friend class DerivedRegistrar;
};

// Register Derived in the Factory so it will attempt to construct objects.
// This is done by adding the function pointer Derived::ProduceDerivedFromInt
// in the Factory list of registered functions.
struct DerivedRegistrar{ 
    DerivedRegistrar(){ 
        Factory::registerPtr(&(Derived::ProduceDerivedFromInt));
    }
} derivedregistrar;

int main(){
    // attempt to produce a Derived object from 1: should fail
    Base* test=Factory::Produce(1);
    std::cout << test << std::endl; // outputs 0

    // attempt to produce a Derived object from 0: works
    test=Factory::Produce(0);
    std::cout << test << std::endl; // outputs an address
}

TL; DR: в этом подходе разработчикам нижестояния необходимо реализовать производящую функцию производного класса в качестве функции-члена static (или не-членной функции) и зарегистрировать ее в factory с помощью простого struct.

Это кажется достаточно простым и не требует каких-либо объектов-прототипов.

Ответ 5

Вот устойчивая идиома для управления фабриками, которые разрешаются во время выполнения. Я использовал это в прошлом для поддержки довольно сложного поведения. Я выступаю за простоту и удобство обслуживания, не отказываясь от функциональности.

TL;DR:

  • Избегайте статической инициализации вообще
  • Избегайте методов "автоматической загрузки", таких как чума.
  • Сообщать владение объектами и фабриками
  • Отдельное использование и factory проблемы управления

Использование загородных заведений

Вот базовый интерфейс, с которым будут взаимодействовать пользователи этой системы factory. Им не нужно беспокоиться о деталях factory.

class BaseObject {
public:
    virtual ~BaseObject() {}
};

BaseObject* CreateObjectFromStream(std::istream& is);

В стороне, я бы рекомендовал использовать ссылки, boost::optional или shared_ptr вместо необработанных указателей. В идеальном мире интерфейс должен сказать мне, кому принадлежит этот объект. Как пользователь, я отвечаю за удаление этого указателя, когда он мне дал? Это болезненно ясно, когда он shared_ptr.

Внедрение закладок времени выполнения

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

class RuntimeFactory {
public:
    virtual BaseObject* create(std::istream& is) = 0;
};

void RegisterRuntimeFactory(RuntimeFactory* factory);
void UnregisterRuntimeFactory(RuntimeFactory* factory);

Я думаю, что основной момент во всем этом заключается в том, что использование - это другое беспокойство от того, как фабрики инициализируются и используются.

Следует отметить, что владельцам этих свободных функций принадлежат фабрики. Реестр не владеет ими.

Это не является строго необходимым, хотя он предлагает больше контроля, когда и где эти заводы уничтожаются. То, что имеет значение, - это когда вы видите такие вещи, как призывы "после создания" или "до уничтожения". factory методы с подобными именами представляют собой запахи дизайна для инверсии собственности.

Написание другой обертки вокруг этого для управления жизненным циклом заводов будет достаточно простым в любом случае. Это также придает композиции, что лучше.

Регистрация нового Factory

Напишите обертки для каждой регистрации factory. Обычно я помещаю каждую запись factory в свой собственный заголовок. Эти заголовки обычно представляют собой только два вызова функций.

void RegisterFooFactory();
void UnregisterFooFactory();

Это может показаться излишним, но такое усердие ведет к сокращению времени компиляции.

My main затем сводится к кучке регистров и незарегистрированных вызовов.

#include <foo_register.h>
#include <bar_register.h>

int main(int argc, char* argv[]) {
    SetupLogging();
    SetupRuntimeFactory();
    RegisterFooFactory();
    RegisterBarFactory();

    // do work...

    UnregisterFooFactory();
    UnregisterBarFactory();
    CleanupLogging();
    return 0;
}

Избегать ошибок статического ввода

Это позволяет избегать объектов, созданных во время статической загрузки, как некоторые из других решений. Это не случайность.

  • Спецификация С++ не даст вам полезных заверений о том, когда произойдет статическая загрузка.
  • Вы получите трассировку стека, если что-то пойдет не так.
  • Код прост, прям, прост в использовании

Реализация реестра

Детали реализации довольно мирские, как вы думаете.

class RuntimeFactoryRegistry {
public:
    void registerFactory(RuntimeFactory* factory) {
        factories.insert(factory);
    }

    void unregisterFactory(RuntimeFactory* factory) {
        factories.erase(factory);
    }

    BaseObject* create(std::istream& is) {
        std::set<RuntimeFactory*>::iterator cur = factories.begin();
        std::set<RuntimeFactory*>::iterator end = factories.end();
        for (; cur != end; cur++) {
            // reset input?
            if (BaseObject* obj = (*cur)->create(is)) {
                return obj;
            }
        }
        return 0;
    }

private:
    std::set<RuntimeFactory*> factories;
};

Это предполагает, что все заводы взаимоисключающие. Расслабление этого предположения вряд ли приведет к созданию хорошего программного обеспечения. Я бы, наверное, предъявил более жесткие претензии, хе-хе. Другой альтернативой было бы вернуть список объектов.

Нижеприведенная реализация является статической для простоты демонстрации. Это может быть проблемой для многопоточных сред. Он не обязательно должен быть статичным, и я не рекомендую его ставить или не должен быть статичным, он просто здесь. На самом деле это не предмет обсуждения, поэтому я оставлю это на этом.

Эти свободные функции действуют только как сквозные функции для этой реализации. Это позволяет вам unit test реестра или повторно использовать его, если вы так склонны.

namespace {

    static RuntimeFactoryRegistry* registry = 0;

} // anon    

void SetupRuntimeFactory() {
    registry = new RuntimeFactoryRegistry;
}

void CleanupRuntimeFactory() {
    delete registry;
    registry = 0;
}

BaseObject* CreateObjectFromStream(std::istream& is) {
    return registry->create(is);
}

void RegisterRuntimeFactory(RuntimeFactory* factory) {
    registry->registerFactory(factory);
}

void UnregisterRuntimeFactory(RuntimeFactory* factory) {
    registry->unregisterFactory(factory);
}

Ответ 6

Во-первых, здесь нет достаточно подробностей, чтобы сформировать мнение, поэтому мне остается угадать. Вы задали сложный вопрос и минимальное решение, но не выяснили, что не так с вашим решением.

Я подозреваю, что жалоба сосредотачивается вокруг reset назад, ничего не зная, что между отказом строительства и последующими попытками строительства. Учитывая очень большое количество потенциальных заводов, этот reset мог бы нам анализировать одни и те же данные сотни или тысячи раз. Если это проблема, возникает вопрос: как вы структурируете фазу оценки предикатов, чтобы ограничить объем работы и позволить ей повторно использовать предыдущие результаты синтаксического анализа.

Я предлагаю иметь каждый регистр factory с: 1) функция builder factory, которая принимает параметр специализации (iostream в примере) 2) неупорядоченный набор булевых предикатов 3) требуемые логические значения для каждого предиката, чтобы построить конструкцию

Набор предикатов используется для создания/изменения дерева предикатов. Внутренние узлы в дереве представляют предикаты (разветвление на "pass", "fail" и, возможно, "не волнует" ). Внутренние узлы и листья содержат конструкторы, которые удовлетворяются, если предикаты предков удовлетворяются. Когда вы пересекаете дерево, вы сначала смотрите на конструкторы на текущем уровне, затем оцениваете предикат и следуете требуемому пути. Если решение не найдено вдоль этого дочернего пути, следуйте по пути "не заботьтесь".

Это позволяет новым фабрикам совместно использовать предикатные функции. Вероятно, есть много вопросов об управлении/сортировке дерева, когда фабрики идут вкл/выкл. Также существует возможность сохранения данных состояния парсера, которые должны сохраняться в предикатах, и reset, когда строительство завершено. Там много открытых вопросов, но это может работать для решения проблем, связанных с вашим решением.

TL: DR; Создайте график предикатов для перемещения при попытке построения.

Ответ 7

Простое решение - это всего лишь коммутатор:

Base *create(int type, std::string data) {
  switch(type) { 
   case 0: return new Derived1(data); 
   case 1: return new Derived2(data);
  };
}

Но тогда он просто решает, какой тип вы хотите:

   int type_of_obj(string s) {
      int type = -1;
      if (isderived1(s)) type=0;
      if (isderived2(s)) type=1;
      return type;
   } 

Затем он просто соединяет два:

Base *create_obj(string s, string data, 
                 Base *(*fptr)(int type, string data), 
                 int (*fptr2)(string s)) 
{
   int type = fptr2(s);
   if (type==-1) return 0;
   return fptr(type, data);   
}

Затем он просто регистрирует указатели на функции:

   class Registry {
   public:
       void push_back(Base* (*fptr)(int type, string data),
                      int (*fptr2)(string s));
       Base *create(string s, string data);
   };

Плагин будет иметь 2 функции и следующее:

void register_classes(Registry &reg) {
    reg.push_back(&create, &type_of_obj);
    ...
}

Плагин загрузчик будет dlopen/dlsym функции register_classes.

(с другой стороны, я сам не использую такие плагины, потому что создание новых плагинов - это слишком много работы. У меня есть лучший способ обеспечить модульность для моих частей программы. Что убивает плагины, так это то, что вам нужно измените вашу систему сборки, чтобы создать новую dll или shared_libs, и это слишком много работает - в идеале новый модуль - это всего лишь один класс, без каких-либо более сложных модификаций системы сборки)