Elegant way to implement extensible factories in C++

前端 未结 7 1013
刺人心
刺人心 2020-12-07 14:50

I am looking for an intuitive and extensible way to implement factories for subclasses of a given base class in c++. I want to provide such a factory function in a library.T

相关标签:
7条回答
  • 2020-12-07 15:53

    My comments were probably not very clear. So here is a C++11 "solution" relying on template meta programming : (Possibly not the nicest way of doing this though)

    #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;
    }
    

    Advantages:

    1. No (additional) run-time overhead as you would have with the function pointers. Works for any base type, and for any number of derived types. You still end up calling the functions of course.
    2. Thanks to variadic templates this works with any number of arguments (giving an incorrect number of arguments will produce a compile-time error message).
    3. Explicit registering of the produce member functions is not required.

    Disadvantages:

    1. All of your derived types must be available when you declare the Factory type. (You must know what the possible derived types are and they must be complete.)
    2. The produce member functions for the derived types must be public.
    3. Can make compilation slower. (As always the case when relying on template metaprogramming)

    In the end, using the prototype design pattern might turn out better. I don't know since I haven't tried using my code.

    I'd like to state some additional things (after further discussion on the chat):

    • Each factory can only return a single object. This seems strange, as the users decide whether they will take the input to create their object or not. I would for that reason suggest your factory can return a collection of objects instead.
    • Be careful not to overcomplicate things. You want a plugin system, but I don't think you really want factories. I would propose you simply make users register their classes (in their shared object), and that you simply pass the arguments to the classes' Produce (static) member functions. You store the objects if and only if they're not the nullptr.
    0 讨论(0)
提交回复
热议问题