0

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 Class RegisterALL).

Ex: RegisterALL class will initialize ARegister and BRegister. So anybody using the module need not create objects of ARegister and BRegister individually, instead they can create object of RegisterALL.

I would like to do all these registrations in the constructor. So all that needs to be done is to create objects of the RegisterALL class and the registrations happens automatically.

We do not use exceptions in our project. So If there is some error in the registration then it is not possible to know.

Should I have separate member function that will do the registration or let the constructor do the registration.

I am a newbie to OO design. I feel something is wrong with the design, it will be helpful if you can point to the right approach.

zinking
  • 5,561
  • 5
  • 49
  • 81
Saaras
  • 379
  • 5
  • 17
  • Could you go back and accept some answers? This will encourage people to help you. –  Jun 05 '12 at 21:54
  • "We do not use exceptions in our project." Is there a good reason for this? Do you disable all exceptions (e.g., if `new` fails, does it throw? Return `NULL`? Do you not use `new`?) – James McNellis Jun 05 '12 at 21:54
  • Yes we use "new". We check for NULL after initializing an object with "new". – Saaras Jun 05 '12 at 21:57
  • 2
    If you can't/don't have exceptions the normal way is to have an init() function returning a bool. But be careful of copy ctor – Martin Beckett Jun 05 '12 at 22:03
  • 1
    When I was new to OO, I tried to write everything OO-style, which was too hard. Eventually, I learned to write the most important parts OO-style, leaving less important code in the old style. I gained experience in this way and, gradually, I began to write more and more OO. What I am trying to say is this: you may not wish to OO-ify all your code at once, but to start by OO-ifying a manageable piece of it. – thb Jun 05 '12 at 22:16
  • Your specific question seems to offer too little information for a precise answer, so let me ask this: What would happen if you tried writing just ARegister and BRegister OO-style -- if you wrote `register_all()` in the old, procedural way? Would it be easier for you to make this hybrid work? If it would, then you might try that. C++ is specially designed to support exactly such a hybrid approach. Good luck. – thb Jun 05 '12 at 22:20
  • The only responsibility of these classes is to register. Currently they dont have any other member functions. Is this ok? – Saaras Jun 05 '12 at 22:29

2 Answers2

3

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.

Benjy Kessler
  • 7,356
  • 6
  • 41
  • 69
3

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.

jxh
  • 69,070
  • 8
  • 110
  • 193
  • Lots of good stuff to learn from the code. I am not sure how to accept 2 answers. – Saaras Jun 06 '12 at 15:24
  • @Saaras: Don't sweat it. You pick what you feel provides you with the best answer to your question. – jxh Jun 06 '12 at 15:26
  • @Saaras: I've finished with this example. Let me know if you have questions. – jxh Jun 07 '12 at 17:13