views:

161

answers:

8

I need to call a virtual method for all classes derived from a given base base class right after the construction of the derived object. But doing so in the base class constructor will result in a pure virtual method call

Here is a simplified example:

struct Loader {
    int get(int index) { return 0; }
};

struct Base{
    Base() {
        Loader l; 
        load( l ); // <-- pure virtual call!
    }
    virtual void load( Loader & ) = 0;
};

struct Derived: public Base {
    int value;
    void load( Loader &l ) {
        value = Loader.get(0);
    }
};

I can call load at the Derived constructor, but Derived could not know how to create a Loader. Any ideas/workarounds?

A: 

Many known frameworks (like MFC) do this: They make a (virtual) member-function Init() or Create() and do the initialization there and then mandate in the documentation that the user call it. I know you won't like this idea, but you just can't call a virtual method from a constructor and expect it to behave polymorphically, regardless of the methods pureness...

Armen Tsirunyan
+6  A: 

The problem is that base class construction occurs before the derived class is fully constructed. You should either call "load" from the derived class, initialise throguh a different virtual member function or create a helper function to do this:

Base* CreateDerived()
{
    Base* pRet = new Derived;
    pRet->Load();
    return pRet;
}
Goz
Ok, I managed to get this far, but how can I prevent Derived from being created elsewere, and therefore being used without proper initialization?
Vargas
@Vargas: For every class derived from Base make the constructor private and define a `Base* CreateDerivedX()` and make it a friend.
Eugen Constantin Dinca
@Eugen, can this be done without needing to change every derived class?
Vargas
Eugen Constantin Dinca
@Goz: As it is when I'm writing this, the above code leaks memory (and possibly other resources) if `Load` throws an exception. It's possible to fix that particular problem, but that problem is only one -- typical -- manifestation of the sheer brittleness of two-phase construction. Don't use two-phase construction when it's easy to avoid, as it is in most cases including this. Think of two-phase as a very sexy woman with a host of veneral diseases. Just say "no" to her, however alluring she may be. Cheers,
Alf P. Steinbach
Make CreateDerived a public static member function of Derived, make the constructors of Derived protected or private. Creating one will look like this `Derived * d = Derived::create()` and trying to create one using `Derived * d = new Derived;` wont be possible due to the private constructors.
Michael Anderson
+2  A: 

Use the PIMPL pattern:

template<typename T>
class Pimpl
{
    public:
        Pimpl()
        {
            // At this point the object you have created is fully constructed.
            // So now you can call the virtual method on it.
            object.load();
        }
        T* operator->()
        {
            // Use the pointer notation to get access to your object
            // and its members.
            return &object;
        }
    private:
        T    object;   // Not technically a pointer
                       // But otherwise the pattern is the same.
                       // Modify to your needs.
};

int main()
{
    Pimpl<Derived>   x;
    x->doStuff();
}
Martin York
How can I use this and prevent Derived from being created somewhere else?
Vargas
Make the constructor private. And then make Pimpl<Derived> a friend of Derived and Pimpl<Derived2> a friend of Derived2 etc.
Martin York
Whithout needing to modify every derived class, there's a way? Maybe somehow to make Pimpl<Base> a friend of Base?
Vargas
+3  A: 

The C++ FAQ calls this problem DBDI, Dynamic Binding During Construction. Mainly, the problem is to avoid the Evil two-phase construction advocated in other answers here. It's sort of "my" FAQ item -- I convinced Marshall to add it.

However, Marshall's take it on it is very general (which is good for a FAQ), while I was more concerned with the particular design/coding pattern.

So, instead of sending you to the FAQ I send you to my own blog, the article "How to avoid post-construction by using Parts Factories", which links to the relevant FAQ item, but discusses in depth the pattern.

You can just skip the first two paragraphs...

I sort of rambled there. :-)

Cheers & hth.,

Alf P. Steinbach
+1  A: 

Can't you add a method getLoader() in your Base class so that DerivedClass constructor can call it on this to get a Loader ? As DerivedClass constructor will be called after Base class constructor, that should work fine.

Benoit Thiery
+1  A: 

Its hard to give advice unless you tell us what you are trying to accomplish, rather than how. I find that its usually better to construct such objects from a factory, which will load the required data before-hand, and then pass the data into the constructor of the object.

Kent Murra
A: 

There are many ways to correct this, here is 1 suggestion fitting within your provided framework

struct Loader {
    int get(int index) { return 0; }
};

struct Base{
    Base() {
    }
    Loader & getLoader( );
private:
    Loader l; 
};

struct Derived: public Base {
    int value;
    Derived( ) {
        value = getLoader().get(0);
    }
};
Greg Domjan
Note: your `Derived` constructor is not properly declared. Also, if the `Loader` instance is expensive and should not be alive for too long (i.e. reads from an XML or JSON file), this will be a problem. However, thumbs up for a backward compatible solution!
André Caron
Certainly the lifetime of the Loader object changes, you raise a good point on potential the associated cost of that.
Greg Domjan
A: 

This may come a little late after other answers, but I'll still give it a try.

You can implement this safely, and without changing derived classes. However, you will need to change use of all these classes, which might be far worse, depending on your scenario. If you are still designing, then this might be viable alternative.

Basically, you can apply the curiously recurring template pattern and inject the initialization code after the constructor gets invoked. Furthermore, if you do it as I've written it below, you can even protect load from being called twice.

struct Loader {
    int get(int index) { return 0; }
};
struct Base {
    virtual ~Base() {} // Note: don't forget this.
protected:
    virtual void load( Loader & ) = 0;
};

struct Derived : public Base {
    int value;
protected:
    void load( Loader &l ) {
        value = l.get(0);
    }
};

template<typename T>
class Loaded : public T
{
public:
    Loaded () {
        Loader l; T::load(l);
    }
};

int main ( int, char ** )
{
    Loaded<Derived> derived;
}

Frankly, though, I would consider an alternate design if you can. Move the code from load to your constructors and provide the loader as an a reference argument defaulting as follows:

struct Derived : public Base {
     Derived ( Loader& loader = Loader() ) { ... }
}; 

That way, you completely avoid the problem.

Summary: your choices are the following:

  1. If you are not limited by external constraints and don't have an extensive code base depending on this, change your design for something safer.
  2. If you want to keep load the way it is and not change your classes too much but are willing to pay the price of changing all instantiations, apply CRTP as proposed above.
  3. If you insist on being mostly backward compatible with existing client code, you will have to change you classes to use a PIMPL as others have suggested or live with the existing problem.
André Caron
Also, if you want to make sure objects are not created without being loaded, declare constructors as protected. This, however, is *not* fail-safe because other clients can simply derive from your class and do whatever they want, including not call `load` or call it twice.
André Caron
Alf P. Steinbach
@Alf: I left the `const` out because in the problem statement, `Loader::get()` was not `const`. But you're right, it is invalid syntax and I should have put it anyways. As for your post, I also checker your blog. I've used the exact same design in the exact same type of library, so I totally agree. I posted these two alternatives because I find they take up less code to write.
André Caron