构造函数中的虚方法

时间:2013-01-31 07:49:01

标签: c++ constructor virtual-method

禁止在C ++中调用虚方法,但有一些情况可能非常有用。

考虑以下情况 - 父类和子类对。父构造函数需要实例化Child类,因为它必须以特殊方式初始化它。父元素和子元素都可以派生,以便DerivedParent使用DerivedChild。

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

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

另一方面,派生类应该能够指定样式和扩展样式,窗口的类名等。

如果在Control的构造函数中构造窗口的实例是要完成的契约,我认为除了在ctor中使用VM之外没有其他解决方案(什么是行不通的)。

可能的解决方法:

  • 使用静态多态(例如,类Derived:public Base),但如果想要派生自Derived,它将无效;
  • 如果可能的话,将lambda从派生到基础ctor传递 - 它会起作用,但它是一个完整的核心解决方案;
  • 将大量参数从派生到基本ctor传递 - 它应该有效,但既不优雅也不易使用。

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

如何克服这种语言限制?


编辑:回答答案:

  

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

它会起作用,但它不是一个选项 - 引用着名的C ++ FAQ:

  

第一个变体最初是最简单的,尽管实际上想要创建对象的代码需要一点点程序员自律,这在实践中意味着你注定要失败。说真的,如果只有一两个地方实际上创建了这个层次结构的对象,那么程序员自律是非常本地化的,不会引起问题。

     

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

这不是一个选项,因为它只对基类及其直系后代起作用一次。

  

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

到目前为止,这是最好的解决方案 - 将代码注入基础ctor。在我的情况下,我甚至不需要这样做,因为我可以参数化基本ctor并传递后代的值。但它确实会奏效。

  

使用工厂函数模板在返回父级之前生成相应类型的子指针。这消除了类模板的复杂性。使用类型特征模式对子类和父类进行分组。

是的,但它有一些缺点:

  • 从我的班级派生的人可以发布他的ctor并绕过安全措施;
  • 它会阻止使用引用,人们必须使用智能指针。

1 个答案:

答案 0 :(得分:9)

  

禁止在C ++中调用虚方法,但有一些情况可能非常有用。

不,在构造函数中调用虚方法会调度到派生最多的完整对象,这是正在构建的对象。它不是被禁止的,它是明确定义的,它是唯一有意义的东西。

不管是好还是坏,都不允许C ++基类知道最派生对象的身份。在模型下,派生对象在基础构造函数运行之后才开始存在,因此没有任何内容可以获取类型信息。

您的案例中有一些替代方案是:

  1. 从派生构造函数中调用CreateChild。您已经要求CreateChild 定义,因此这是一个增量步骤。您可以在基类中添加protected: void init()函数以保持此类初始化的封装。
  2. 使用CRTP。使基类成为模板,让子类提供DerivedType,并以这种方式调用构造函数。这种设计有时可以完全消除虚拟功能。
  3. 使子指针成为构造函数参数,例如工厂函数。
  4. 使用工厂函数模板在返回父级之前生成相应类型的子指针。这消除了类模板的复杂性。使用类型特征模式对子类和父类进行分组。