简体   繁体   中英

Call virtual method immediately after construction

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?

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;
}

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.,

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();
}

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.

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.

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...

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);
    }
};

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM