Call virtual method immediately after construction
I need t开发者_开发知识库o 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...
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:
- 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.
- 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. - 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.
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);
}
};
精彩评论