C++ certainly provides sophisticated mechanisms to create and destroy objects via it’s constructors and destructors. There are, however, certain aspects to these which make them hardly usable in advanced OO systems. First and foremost, a C++ constructor implementation lacks one very important feature: polymorphic calls! In a constructor, a call to a virtual function of the object constructed, is not a polymorphic call but a call to the method as overridden at the level of the class currently constructing the object! This makes it impossible to give the more abstract constructor information from the more concrete class. Imagine you have a UI element that need size information during construction, but that size information can only be provided by it’s specific subclasses, then you will need to implement a 2-phased approach with construction first and using (displaying in this case) second.
C++ constructors also lack in contrast for instance to Java the possibility to call another constructor from the same class. You can only call constructors from super classes. This makes it harder to initialize a large number of members because the initializers have to be implemented redundantly in every constructor. Without a code generator this is very error prone.
There is another problem which is the rather weird logic of which members get initialized and which do not. If you don’t have a constructor which initializes pointers, then these pointers are not initialized to 0. However, if a class uses some struct as a member and this struct has a pointer as member, then this pointer is initialized to 0.
So, a general approach to get over all these problems would be to not use constructors at all and delegate all responsibilities to some generic virtual methods. Let’s first look at the requirements to an OO lifecycle in general:
- Create all mandatory child objects upon creation of the parent
- If an instance requiring a parent element is created, the parent is already present
- Take a partial tree out of an object tree and connect it to a different object tree (reparenting)
- Isolate a subtree and destroy it
These requirements can be translated into a general lifecycle model in OO systems:
Objects are, once allocated via operator new() in an intermediate state “allocated”, where they can be attached to a parent object, i.e., the parent becomes known to the child and can be used in the subsequent init() call which initializes the object structure in it’s depth. Instead of deleting an object via operator delete() it is destroyed by the destroy() method. In the destroy method, all child objects get destroyed and all destroyed objects are returned to the object pool as isolated instances. Only the pool deletes these instances if needed. Instances are never created via operator new() directly, they are always obtained from the pool via the factory method instance() of the class. This factory method creates a new instance if none is found to be recycled from the pool. Both the default constructor and the destructor are declared as private to ensure the sole usage of the factory method.
This would further lead to the 2 virtual methods init() and destroy() to be implemented on each class level. These methods would have the following behavioral characteristics:
- is called only after business parent has been set
- is the constructor equivalent
- init() calls super::init() first, just like a constructor
- init() initializes members of the class
- init() can be called repeatedly
- memory allocation is a separated concern
- init() is virtual
- init() can call virtual methods
- is called before business parent is removed
- is the destructor equivalent
- destroy() first resets own members
- destroy() calls super::destroy() second like a destructor
- destroy() can be called repeatedly
- memory deallocation is a separated concern
- destroy() is virtual
- destroy() can call virtual methods
- Constructors/destructors are not needed any more, except for memory allocation
- Instances are created in the pool only (i.e., in the class)
- Objects have either a business parent or are referenced by the pool during destroy()
- objects are returned to the pool
- if objects do not have a business parent, then a parent is also not needed for initialization
These feature improve the handling of complex object structures in a meta-driven OO environment applied to C++. In fact, they are so generic that they can be easily adapted in a completely different programming and runtime environment. There are, of course, some drawbacks which is basically the more complex protocol for initialization and the implementation of the object pool.