6

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?

Radu094
  • 28,068
  • 16
  • 63
  • 80
  • a) spell check, b) give `Base` a virtual destructor. – Kerrek SB Jul 05 '11 at 12:03
  • Are you aware that virtual functions are not virtual during constructor and destructor call? – Tadeusz Kopec for Ukraine Jul 05 '11 at 12:03
  • Yes I am. That is why I'm trying to find another way :-p – Radu094 Jul 05 '11 at 12:05
  • 1
    @Tadeysz, Radu094: "not virtual" is a common misconception (i.e. it's incorrect). There's no difference at all in how virtual functions behave, they're as virtual as ever, and supporting virtual calls as always. The only thing that varies is the *dynamic type* of the object. – Cheers and hth. - Alf Jul 05 '11 at 12:37
  • It's not just the type of the object, it's the entire virtual function table that is the problem.The way it is actually setup in my real implementation it seems the virtual method is called BEFORE being setup in the vftable, so I get a runtime exception "Not Implemented". – Radu094 Jul 05 '11 at 14:22
  • @Radu094 Do you have a pure virtual function? Can you show complete code? You may want to use the "Ask Question" button. – curiousguy Nov 30 '11 at 05:37

4 Answers4

8

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)

Traits

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

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Of course this no longer works when the ID is actually something dynamic. But for the use-case at hand this is the perfect solution. – Konrad Rudolph Jul 05 '11 at 12:02
  • 1
    I extended my answer with some thoughts, including the thought of keeping the GetClassID function an instance member – sehe Jul 05 '11 at 12:07
  • hehe... nice! Yes, the ID is static per each class, so this will work. Wondering if I can have templated constructors in a non-templated class?I'd rather not "templatize" by Base class – Radu094 Jul 05 '11 at 12:08
  • @Konrad Rudolph: If it is something dynamic there are very few things that you can do, since the type of the object changes during construction (i.e. when the base is being initialized, it is of type `Base` not `MostDerivedTypeEver`) Of course, it is tricky if `GetClassID` uses any state from the object as the object for that same particular reason: the derived object is still not initialized, but in this case it is a static function. – David Rodríguez - dribeas Jul 05 '11 at 12:08
  • @sehe: The problem is not calling polymorphically or not, the problem is that while the constructor of `Base` is being executed, the `Derived` object has not yet been initialized, and that means that a call to a member method is undefined behavior. – David Rodríguez - dribeas Jul 05 '11 at 12:09
  • @David: good point. Will provide a better sample based on trais – sehe Jul 05 '11 at 12:12
  • pff.. you killed me with DerivedDerived. Hadn't even thought of that; I do have multiple generations of derived classes.. and each has a number of possible constructors.. OK. I'm going back to the drawing board,this is seriously messed up – Radu094 Jul 05 '11 at 12:13
  • @Radu094: luckily I just made up for my promise to show you the 'traits' based approach. Although I'd seriously consider using David's suggestion of just passing the ID up the constructor chain – sehe Jul 05 '11 at 12:20
  • @sehe: I hope you don't mind the edit, but I felt inclined to downvote for that one paragraph and it would not be fair for the overall answer. – David Rodríguez - dribeas Jul 05 '11 at 13:59
7

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() ) {}
};
curiousguy
  • 8,038
  • 2
  • 40
  • 58
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
4

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.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • wow.. this is pretty slick. I can't use it in this particular case, but this is going in the toolbox of tricks. – Radu094 Jul 05 '11 at 14:55
1

I would put the register to a member function and require people to call it explicitly:

  class Base {
    void reg() { SomeSingleton.RegisterThisObject(GetClassID()); }
  };

The constructor is just not the right place for this kind of stuff:

int main() {
  Derived d;
  d.reg();
}
tp1
  • 1,197
  • 10
  • 17