构造函数中的虚方法 [英] Virtual methods in constructor

查看:73
本文介绍了构造函数中的虚方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

禁止在C ++中调用虚拟方法,但是在某些情况下,它可能非常有用。

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

请考虑以下情况-一对Parent和Child类。父构造函数需要实例化Child类,因为它必须以特殊方式对其进行初始化。

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.

但是有一个问题-因为在DerivedParent :: ctor的Parent :: ctor调用中,基类应该有一个实例的DerivedChild而不是Child。但这需要以某种方式调用虚拟方法,这是禁止的。我在说这样的事情:

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

让我们举一个真实的例子。假设我想为WinApi的窗口编写包装器。基本Control类应注册类并实例化窗口(例如RegisterClassEx和CreateWindowEx),以正确设置它(例如,以这种方式注册类,即窗口结构具有用于类实例的其他数据;为所有控件设置通用WndProc ;请参考SetWindowLongPtr等对 this 的引用。)

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.

如果在Control的构造函数中构造window实例是要履行的合同,那么除了在ctor中使用VM,我认为没有其他解决方案(不会

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

可能的解决方法:


  • 使用静态多态性(例如,派生类:public Base),但是如果想要从Derived派生,将不起作用;

  • 如果可能,将lambda从派生传递给基本ctor-会起作用,但这是一个总的核心解决方案;

  • 将大量参数从派生值传递到基本ct或-它应该可以使用,但既不会优雅也不容易使用。

我个人都不喜欢它们。出于好奇,我检查了如何在Delphi的VCL中解决问题,并且您知道什么,基类调用CreateParams,这是虚拟的(Delphi允许此类调用并保证它们是安全的-创建时将类字段初始化为0 )。

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?

编辑:在答案的答案:


从派生的构造函数中调用CreateChild。您已经需要定义CreateChild,因此这是一个增量步骤。您可以在基类中添加一个protected:void init()函数来封装此类初始化。

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.

它可以工作,但不是可选项-引用著名的C ++常见问题解答:

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.

使用CRTP。使基类成为模板,让子级提供DerivedType,然后以这种方式调用构造函数。这种设计有时可以完全消除虚拟功能。

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.


<到目前为止,这是最好的解决方案-将代码注入基本ctor。就我而言,我什至不需要这样做,因为我可以参数化基本ctor并传递后代的值。

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:


  • 从我的班级派生的某人可以发布其ctor并绕过安全措施;

  • 这将阻止一个人使用引用,一个人必须使用智能指针

推荐答案


禁止在C ++中调用虚拟方法。在某些情况下,它可能会非常有用。

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.

不允许C ++基类知道大多数派生对象的标识,无论好坏。在该模型下,直到基本构造函数运行之后,派生对象才开始存在,因此没有任何内容可以获取有关类型的信息。

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. 从派生的构造函数中调用CreateChild。您已经需要对CreateChild进行定义,因此这是一个增量步骤。您可以在基类中添加一个 protected:void init()函数,以保持对此类初始化的封装。

  2. 使用CRTP。使基类成为模板,让子级提供DerivedType,然后以这种方式调用构造函数。这种设计有时可以完全消除虚拟函数。

  3. 使子指针成为构造函数参数,例如指向工厂函数。

  4. 使用工厂函数模板,以在返回父级之前生成适当类型的子级指针。这消除了类模板的复杂性。使用类型特征模式将子类和父类分组。

  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.

这篇关于构造函数中的虚方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆