Virtual methods in constructor

99封情书 提交于 2019-12-11 19:07:25

问题


Calling virtual methods in C++ is prohibited, however there are a few situations, where it might be very useful.

Consider the following situation - pair of Parent and Child classes. Parent constructor requires a Child class to be instantiated, because it has to initialize it in a special way. Both Parent and Child may be derived, such that DerivedParent uses DerivedChild.

There's a problem, however - because in Parent::ctor call from DerivedParent::ctor, base class should have an instance of DerivedChild instead of Child. But that would require calling a virtual method some way, what is prohibited. I'm talking about something like this:

class Child 
{ 
public:
    virtual std::string ToString() { return "Child"; }
};

class DerivedChild : public Child 
{
public:
    std::string ToString() { return "DerivedChild"; }
};

class Parent
{
protected:
    Child * child;

    virtual Child * CreateChild() { return new Child(); }

public:
    Parent() { child = CreateChild(); }
    Child * GetChild() { return child; }
};

class DerivedParent : public Parent
{
protected:
    Child * CreateChild() { return new DerivedChild(); }
};

int main(int argc, char * argv[])
{
    DerivedParent parent;

    printf("%s\n", parent.GetChild()->ToString().c_str());

    getchar();
    return 0;
}

Let's get a real-world example. Suppose, that I want to write wrapper for WinApi's windows. The base Control class should register class and instantiate a window (eg. RegisterClassEx and CreateWindowEx), to properly set it up (for example register class in such way, that window structure has additional data for class instance; set up generic WndProc for all Controls; put reference to this by SetWindowLongPtr etc.)

On the other hand, derived class should be able to specify styles and extended styles, class name for window etc.

If constructing instance of window in the Control's constructor is a contract to be fulfilled, I see no other solution than to use VMs in ctor (what won't work).

Possible workarounds:

  • Use static polymorphism (eg. class Derived : public Base), but it will not work, if one wants to derive from Derived;
  • Pass a lambda from derived to base ctor if it is possible - it will work, but it's a total hardcore solution;
  • Pass a ton of parameters from derived to base ctor - it shall work, but won't be neither elegant, nor easy to use.

I personally don't like neither of them. Out of curiosity, I checked, how the problem is solved in Delphi's VCL, and you know what, base class calls CreateParams, which is virtual (Delphi allows such calls and guarantees, that they are safe - class fields are initialized to 0 upon creating).

How can one overcome this language restriction?


Edit: In response to answers:

Call CreateChild from the derived constructor. You're already requiring CreateChild to be defined, so this is an incremental step. You can add a protected: void init() function in the base class to keep such initialization encapsulated.

It will work, but it's not an option - quoting the famous C++ FAQ:

The first variation is simplest initially, though the code that actually wants to create objects requires a tiny bit of programmer self-discipline, which in practice means you're doomed. Seriously, if there are only one or two places that actually create objects of this hierarchy, the programmer self-discipline is quite localized and shouldn't cause problems.

Use CRTP. Make the base class a template, have the child supply the DerivedType, and call the constructor that way. This kind of design can sometimes eliminate virtual functions completely.

It's not an option, because it will work only once, for base class and its immediate descendant.

Make the child pointer a constructor argument, for example to a factory function.

This is, so far, the best solution - injecting code into base ctor. In my case, I won't even need to do it, because I can just parametrize base ctor and pass values from the descendant. But it will actually work.

Use a factory function template to generate the child pointer of the appropriate type before returning a parent. This eliminates the complexity of a class template. Use a type traits pattern to group child and parent classes.

Yea, but it has some drawbacks:

  • Someone, who derives from my classes may publish his ctor and bypass the security measures;
  • It would prevent one from using references, one would have to use smart pointers.

回答1:


Calling virtual methods in C++ is prohibited, however there are a few situations, where it might be very useful.

No, calling a virtual method in the constructor dispatches to the most-derived complete object, which is the one under construction. It's not prohibited, it's well-defined and it does the only thing that would make sense.

A C++ base class is not allowed to know the identity of the most derived object, for better or worse. Under the model, the derived object doesn't start to exist until after the base constructors have run, so there's nothing to get type information about.

Some alternatives in your case are:

  1. Call CreateChild from the derived constructor. You're already requiring CreateChild to be defined, so this is an incremental step. You can add a protected: void init() function in the base class to keep such initialization encapsulated.
  2. Use CRTP. Make the base class a template, have the child supply the DerivedType, and call the constructor that way. This kind of design can sometimes eliminate virtual functions completely.
  3. Make the child pointer a constructor argument, for example to a factory function.
  4. Use a factory function template to generate the child pointer of the appropriate type before returning a parent. This eliminates the complexity of a class template. Use a type traits pattern to group child and parent classes.


来源:https://stackoverflow.com/questions/14620842/virtual-methods-in-constructor

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!