Initialization class for other classes - C++

后端 未结 2 1046
被撕碎了的回忆
被撕碎了的回忆 2021-01-27 19:38

I would like to initialize 2 classes(say Class ARegister, Class BRegister) that registers some values(A,B). I want to initialize these two classes from a super(?) class( say Cla

相关标签:
2条回答
  • 2021-01-27 20:28

    It seems you have already decided on some kind of relationship between your objects. But, you only vaguely describe the relationship.

    If RegisterALL is using simple containment, then you would have a very simple relationship. This relationship might be expressed like this (please excuse the ASCII graphics):

           +-------------+
           | RegisterALL |               --> := has
           +-------------+
              |       |
              v       v
    +-----------+   +-----------+
    | ARegister |   | BRegister |
    +-----------+   +-----------+
    

    The advantage is that the picture for two dependents is very simple. However, if you are registering many objects, then the picture starts to look like RegisterALL is exploding into a bunch of XRegister objects.

    If RegisterALL is meant to contain ARegister and BRegister, you might want to create a common base class for ARegister and BRegister so that RegisterALL can maintain a container.

          +-------------+      +------------------+            <>--> := aggregates
          | RegisterALL |<>--->| AbstractRegister |              
          +-------------+      +------------------+              |
                                        |                      _/_\_ := inherits
                                       / \
                                   ___/___\___
                                   |         |
                        +-----------+       +-----------+
                        | ARegister |       | BRegister |
                        +-----------+       +-----------+
    

    We see that no matter how many new items get registered, the relationship between RegisterALL and AbstractRegister remains the same. We can go a step further, and derive ARegister and BRegister from a template.

          +-------------+      +------------------+
          | RegisterALL |<>--->| AbstractRegister |
          +-------------+      +------------------+
                                        |
                                       / \
                                      /___\
                                        |
                                        |     +--------------+
                               +--------------| RegisterType |
                               |              +--------------+
                               | RegisterTemplate |
                               +------------------+
    

    Okay, so much for the the OO design lesson. This translates to code pretty quickly. Let's start with the easy things. RegisterType enumerates the different things to register. RegisterTypeName and the overloaded operator allow the code to print a string instead of a number when printing a RegisterType.

    enum RegisterType { A, B, MAX_RegisterType };
    
    static inline std::string
    RegisterTypeName (RegisterType t)
    {
        static const char * names[] = { "A", "B" };
        return names[t];
    }
    
    static inline std::ostream &
    operator << (std::ostream &output, RegisterType t)
    {
        return output << RegisterTypeName(t);
    }
    

    AbstractRegister provides an interface to query for this type. In addition, a poke interface is provided with a default implementation. Note in C++, abstract types should provide a virtual destructor.

    class AbstractRegister {
    public:
        virtual ~AbstractRegister () {}
        virtual RegisterType type () const = 0;
        virtual void poke () { std::cout << "Poked " << type(); }
    };
    
    typedef std::unique_ptr<AbstractRegister> AbstractRegisterPtr;
    static const AbstractRegisterPtr AbstractRegisterNullPtr;
    

    The RegisterALL class has a container to hold things of type AbstractRegister. It uses a map to associate RegisterType to the AbstractRegister instance, which we are taking to be the registration. RegisterALL is implemented as a singleton, meaning it only allows one instance of itself. The add method performs the registration, and the find method allows a registered instance to be found. The implementation of the RegisterALL constructor is deferred until after the definition of a RegisterTemplate.

    class RegisterALL {
        template <RegisterType> friend class RegisterTemplate;
        typedef std::unique_ptr<RegisterALL> SelfPtr;
        typedef std::map<RegisterType, AbstractRegisterPtr> RegisterMap;
        void add (AbstractRegister *r) { all[r->type()] = AbstractRegisterPtr(r); }
        RegisterALL ();
    public:
        static const SelfPtr & instance () {
            if (!one) new RegisterALL;
            return one;
        }
        const AbstractRegisterPtr & find (RegisterType t) const {
            RegisterMap::const_iterator i = all.find(t);
            return (i != all.end()) ? i->second : AbstractRegisterNullPtr;
        }
    private:
        static SelfPtr one;
        RegisterMap all;
    };
    
    RegisterALL::SelfPtr RegisterALL::one;
    

    The RegisterTemplate class derives from the AbstractRegister and is parameterized by a RegisterType. It implements the type virtual method by returning the value of its template parameter. It also employs singleton, but it does not make its instance public. Instead, its instance is managed by RegisterALL. It provides the register_type method that registers itself with RegisterALL, and this instance can only be found by using the find method on RegisterALL.

    template <RegisterType RT>
    class RegisterTemplate : public AbstractRegister {
        RegisterType type () const { return RT; }
        void poke () {
            std::cout << "Poked " << RegisterTypeName(RT) << std::endl;
        }
        RegisterTemplate () {
            std::cout << "Created " << RegisterTypeName(RT) << std::endl;
        }
        ~RegisterTemplate () {
            std::cout << "Destroying " << RegisterTypeName(RT) << std::endl;
        }
    public:
        static void register_type () {
            if (RegisterALL::instance()->find(RT)) {
                std::cout << "Already registered " << RegisterTypeName(RT)
                          << std::endl;
                return;
            }
            RegisterALL::instance()->add(new RegisterTemplate<RT>);
        }
    };
    

    The RegisterALL constructor employs the helper template register_all that iterates through the RegisterType enum, and instantiates the corresponding RegisterTemplate, thereby causing all RegisterType's to be registered with RegisterALL.

    template <unsigned X>
    struct register_all {
        register_all () {
            RegisterTemplate<static_cast<RegisterType>(X)>::register_type();
            register_all<X+1>();
        }
    };
    
    template <> struct register_all<MAX_RegisterType> {};
    
    inline RegisterALL::RegisterALL ()
    {
        one = std::move(SelfPtr(this));
        register_all<0>();
    }
    

    So to try it out, we use the following code:

    RegisterALL::instance();                  // registers all RegisterType's
    RegisterTemplate<B>::register_type();     // try to register B again
    RegisterALL::instance()->find(A)->poke(); // poke at A
    

    And this is the output:

    Created A
    Created B
    Already registered B
    Poked A
    Destroying B
    Destroying A
    

    Notice how the smart pointers automatically clean up the registered items for us.

    0 讨论(0)
  • 2021-01-27 20:31

    the best way do to this is using CRTP (curiously recurring template pattern), the derived classes ARegister and BRegister pass themselves as template arguments to the base class RegisterALL. It will look like this:

    class RegisterAll
    {
    public:
        template<class DerivedType>
        DerivedType *createType()
        {
            RegisterAll *r = (*(m_creators[DerivedType::id]))();
            return dynamic_cast<DerivedType *>(r); //static_cast will work too if you didn't make a mistake
        }
    protected:
        static std::map<int,RegisterAll *(*)()> m_creators;
    };
    
    std::map<int,RegisterAll *(*)()> RegisterAll::m_creators = std::map<int,RegisterAll *(*)()>();
    
    template<class Derived>
    class CRTPRegisterAll : public RegisterAll
    {
    public:
        static bool register() 
        {
            RegisterAll::m_creators.push_back(std::make_pair(Derived::id,Derived::create);
            return true;
        }
    
    private:
        static bool m_temp;
    };
    
    template<class Derived>
    bool CRTPRegisterAll<Derived>::m_temp = CRTPRegisterAll<Derived>::register();
    
    class RegisterA : public CRTPRegisterAll<RegisterA>
    {
    private:
        static RegisterA *create() 
        {
            //do all initialization stuff here
            return new RegisterA;
        }
    
    public:
        static const int id = 0;
    };
    

    Now the initialization of the static variable m_temp in CRTPRegisterAll pushes a creator function for each derived type onto RegisterAll's list. It is generally not very good design to have RegisterAll know about all the derived classes (it isn't very extensible). This way the actual creation method can be implemented in each derived class and the creator function will be automatically registered in RegisterAll. One of the main advantages of CRTP is that the derived classes don't need to know how to register themselves to the base class' list, it is done for them. As for error handling that too can be implemented in each derived class' creator functions. There are better ways to handle the id issue but I don't want to get into that here. If you want a simpler method I suggest reading about the Factory design pattern.

    0 讨论(0)
提交回复
热议问题