Looking for a better C++ class factory

前端 未结 10 671
伪装坚强ぢ
伪装坚强ぢ 2020-12-29 00:40

I have an application that has several objects (about 50 so far, but growing). There is only one instance of each of these objects in the app and these instances get shared

相关标签:
10条回答
  • 2020-12-29 01:12

    The way I would solve this problem is using what I would call the Static Registry Pattern, which in my mine mind is the C++ version of dependency injection.

    Basically you have a static list of builder objects of a type that you use to build objects of another type.

    A basic static registry implementation would look like:

    template <class T>
    class StaticRegistry
    {
    public:
        typedef std::list<T*>   Container;
    
        static  StaticRegistry<T>&  GetInstance()
        {
            if (Instance == 0)
            {
                Instance = new StaticRegistry<T>;
            }
            return *Instance;
        }
    
        void    Register(T* item)
        {
            Items.push_back(item);
        }
    
        void    Deregister(T* item)
        {
            Items.remove(item);
            if (Items.empty())
            {
                delete this;
                Instance = 0;
            }
        }
    
        typedef typename Container::const_iterator  const_iterator;
    
        const_iterator begin() const
        {
            return Items.begin();
        }
    
        const_iterator end() const
        {
            return Items.end();
        }
    
    protected:
        StaticRegistry() {}
        ~StaticRegistry() {}
    
    private:
        Container               Items;
    
        static StaticRegistry<T>*   Instance;
    };
    
    template <class T>
    StaticRegistry<T>* StaticRegistry<T>::Instance = 0;
    

    An implementation of BrokeredObjectBuilder could look like this:

    class BrokeredObjectBuilderBase {
    public:
        BrokeredObjectBuilderBase() { StaticRegistry<BrokeredObjectBuilderBase>::GetInstance().Register(this); }
        virtual ~BrokeredObjectBuilderBase() { StaticRegistry<BrokeredObjectBuilderBase>::GetInstance().Deregister(this); }
    
        virtual int GetInterfaceId() = 0;
        virtual BrokeredObject* MakeBrokeredObject() = 0;
    };
    
    
    template<class T>
    class BrokeredObjectBuilder : public BrokeredObjectBuilderBase {
    public:
        BrokeredObjectBuilder(unsigned long interface_id) : m_InterfaceId(interface_id) { } 
        virtual int GetInterfaceId() { return m_InterfaceId; }
        virtual T* MakeBrokeredObject() { return new T; }
    private:
        unsigned long m_InterfaceId;
    };
    
    
    class TypeA : public BrokeredObject
    {
       ...
    };
    
    // Create a global variable for the builder of TypeA so that it's 
    // included in the BrokeredObjectBuilderRegistry
    BrokeredObjectBuilder<TypeA> TypeABuilder(TypeAUserInterfaceId);
    
    typedef StaticRegistry<BrokeredObjectBuilderBase> BrokeredObjectBuilderRegistry;
    
    BrokeredObject *GetObjectByID(int id)
    {
      BrokeredObject *pObject(0);
      ObjectMap::iterator = m_objectList.find(id);
      // etc.
      if(found) return pObject;
    
      // not found, so create
      BrokeredObjectBuilderRegistry& registry(BrokeredObjectBuilderRegistry::GetInstance());
      for(BrokeredObjectBuilderRegistry::const_iterator it = registry.begin(), e = registry.end(); it != e; ++it)
      {
        if(it->GetInterfaceId() == id)
        {
          pObject = it->MakeBrokeredObject();
          break;
        }
      }
    
      if(0 == pObject)
      {
        // userinterface id not found, handle this here
        ...
      }      
    
      // add it to the list
      return pObject;
    }
    

    Pros:

    • All the code that knows about creating the types is seperated out into the builders and the BrokeredObject classes don't need to know about it.
    • This implementation can be used in libraries and you can control on a per project level what builders are pulled into a project using a number of different techniques.
    • The builders can be as complex or as simple (like above) as you want them to be.

    Cons:

    • There is a wee bit of infrastructure involved (but not too much).
    • The flexability of defining the global variables to include what builders to include in your project does make it a little messy to work with.
    • I find that people find it hard to understand this pattern, I'm not sure why.
    • It's sometimes not easy to know what is in the static registry at any one time.
    • The above implementation leaks one bit of memory. (I can live with that...)

    The above implementation is very simple, you can extend it in lots of different ways depending on the requirements you have.

    0 讨论(0)
  • 2020-12-29 01:12

    Instead of GetInterfaceId() in the BrokeredObject base class, you could define that pure virtual method:

    virtual BrokeredObject& GetInstance()=0;
    

    And in the derived classes you'll return from that method the instance of the particular derived class, if it's already created, if not, you'll first create it and then return it.

    0 讨论(0)
  • 2020-12-29 01:15

    You should almost certainly be using dependency injection.

    0 讨论(0)
  • 2020-12-29 01:17

    Why not this?

        template 
        BrokeredObject* GetOrCreateObject()
        {
          return new T();
        }
    
    0 讨论(0)
  • 2020-12-29 01:18

    Yes, there is a way. A pretty simple even in C++ to what that C# code does (without checking for inheritance though):

    template<typename T>
    BrokeredObject * GetOrCreateObject() {
      return new T();
    }
    

    This will work and do the same as the C# code. It is also type-safe: If the type you pass is not inherited from BrokeredObject (or isn't that type itself), then the compiler moans at the return statement. It will however always return a new object.

    Singleton

    As another guy suggested (credits to him), this all looks very much like a fine case for the singleton pattern. Just do TypeA::getInstance() to get the one and single instance stored in a static variable of that class. I suppose that would be far easier than the above way, without the need for IDs to solve it (i previously showed a way using templates to store IDs in this answer, but i found it effectively is just what a singleton is).

    I've read that you will leave the chance open to have multiple instances of the classes. One way to do that is to have a Mingleton (i made up that word :))

    enum MingletonKind {
        SINGLETON,
        MULTITON
    };
    
    // Singleton
    template<typename D, MingletonKind>
    struct Mingleton {
        static boost::shared_ptr<D> getOrCreate() {
            static D d;
            return boost::shared_ptr<D>(&d, NoopDel());
        }
    
        struct NoopDel {
            void operator()(D const*) const { /* do nothing */ }
        };
    };
    
    // Multiton
    template<typename D>
    struct Mingleton<D, MULTITON> {
        static boost::shared_ptr<D> getOrCreate() {
            return boost::shared_ptr<D>(new D);
        }
    };
    
    class ImASingle : public Mingleton<ImASingle, SINGLETON> {
    public:
        void testCall() { }
        // Indeed, we have to have a private constructor to prevent
        // others to create instances of us.
    private:
        ImASingle() { /* ... */ }
        friend class Mingleton<ImASingle, SINGLETON>;
    };
    
    class ImAMulti : public Mingleton<ImAMulti, MULTITON> {
    public:
        void testCall() { }
        // ...
    };
    
    int main() {
        // both do what we expect.
        ImAMulti::getOrCreate()->testCall();
        ImASingle::getOrCreate()->testCall();
    }
    

    Now, you just use SomeClass::getOrCreate() and it cares about the details. The custom deleter in the singleton case for shared_ptr makes deletion a no-op, because the object owned by the shared_ptr is allocated statically. However, be aware of problems of destruction order of static variables: Static initialization order fiasco

    0 讨论(0)
  • 2020-12-29 01:19

    It doesn't look like you need the global object to do the management, so why not move everything into the classes themselves?

    template <class Type>
    class BrokeredObject
    {
    protected:
        static Type *theInstance;
    
    public:
        static Type *getOrCreate()
        {
            if (!theInstance) {
                theInstance = new Type();
            }
    
            return theInstance;
        }
    
        static void free()
        {
            delete theInstance;
        }
    
    };
    
    class TestObject : public BrokeredObject<TestObject>
    {
    public:
        TestObject()
        {}
    
    };
    
    
    int
    main()
    {
        TestObject *obj = TestObject::getOrCreate();
    }
    
    0 讨论(0)
提交回复
热议问题