Say I have the following class hierarchy:
class Base
{
virtual int GetClassID(){ return 0;};
public:
Base() { SomeSingleton.RegisterThisObject(this->GetClassID());
}
class Derived
{
virtual int GetClassID(){ return 1;};
public:
Derived():Base(){};
}
Well, it's all simplified from my real case, but that's the general gist of it.
I want to avoid having to call RegisterThisObject in the constructor of each derived class, so I'm trying to move the call to the constructor of the base class.
Is there any pattern that I can use to acomplish this without using the virtual method in the constructor?
You might use the Curiously Recurring Template Pattern
template <class T>
class Base
{
protected: // note change
Base() { SomeSingleton.RegisterThisObject(T::GetClassID());
}
class Derived : Base<Derived>
{
static int GetClassID(){ return 1;};
public:
Derived(): Base<Derived>(){};
}
Also, it will require extra work when you have multiple generations of derived classes (say DerivedDerived : Derived
). I'd suggest you simply avoid that but in other cases you might want to move the registration into a policy class instead (make the behaviour aggregatable as opposed to a part of the class identity)
Expanding on my hint (make the behaviour aggregatable), you'd see something like this:
namespace detail
{
template <class T> struct registerable_traits { };
template<> struct registerable_traits<Derived>
{
enum _id { type_id = 1 };
};
}
template <class T>
class Base
{
protected: // note change
Base() { SomeSingleton::RegisterThisObject(detail::registerable_traits<T>::type_id); }
};
class Derived : Base<Derived>
{
public:
Derived(): Base<Derived>(){};
};
See Codepad.org
The problem with the virtual
approach is that it will not work, since while the base constructor object is being executed, the type of the object is base, and not the derived type.
If the GetClassID
was a static member function, you could change the design so that the identifier is passed as an argument to the base type:
struct Base {
Base( int id ) {
register_object( id, this );
}
};
struct Derived {
static int getId() { return 5; }
Derived() : Base( getId() ) {}
};
The simplest solution for this exact case is just to pass the id as an
argument to Base
, and be done with it. As long as it's just a
question of data, no virtual function is needed. In more complicated
cases, you could pass a pointer to a struct
, or even the address of a
(static member or free) function.
In more complicated cases, the strategy pattern may apply: the actual customization is delegated to a separate hierarchy, and the derived class constructor passes a pointer to a derived delegate. If the delegate has no state, it can be a static instance somewhere.
Finally, if all else fails, you can use a dummy argument (if the derived has no arguments) or wrap an argument. This requires some collaboration from the derived class, and doesn't work well with temporaries, but I've used it successfully once or twice. Basically, you define something like:
class Base
{
public:
class DeferredInit
{
friend class Base;
mutable Base* myOwner;
public:
DeferredInit() : myOwner( NULL ) {}
~DeferredInit()
{
if ( myOwner != NULL ) {
myOwner->postCtor();
}
}
};
Base( DeferredInit const& initializer )
{
initializer.myOwner = this;
}
};
Derived classes are then something like:
class Derived : public Base
{
public:
Derived( Base::DeferredInit const& fromAbove = Base::DeferredInit() )
: Base( fromAbove )
{
}
};
The one time I used this, all of the classes accepted an std::string
as input, so I arranged for DeferredInit
to convert implicitly from
std::string
and char const*
, wrapping the argument. Again, the
client code could just write:
Derived d( "some string" );
and postCtor
was called at the end of the full expression. (That's
why things like:
Derived( "abc" ).doSomething();
don't work. You must declare an instance to be sure that postCtor
is
called before using the object in any other way. No temporaries!)
But I'd only consider this solution as a last resort. It introduces additional complexity, and adds the restriction concerning temporaries.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With