0

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

考虑以下情况 - 一对父类和子类。父构造函数需要实例化一个子类,因为它必须以特殊方式对其进行初始化。Parent 和 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 ;this由 SetWindowLongPtr 等引用)

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

如果在控件的构造函数中构造窗口实例是要履行的合同,我认为除了在 ctor 中使用 VM 之外没有其他解决方案(这是行不通的)。

可能的解决方法:

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

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

如何克服这种语言限制?


编辑:回应答案:

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

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

第一个变体最初是最简单的,尽管实际上想要创建对象的代码需要一点程序员自律,这实际上意味着你注定要失败。说真的,如果只有一两个地方实际创建了这种层次结构的对象,程序员的自律是相当本地化的,应该不会造成问题。

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

这不是一个选项,因为它只适用于基类及其直接后代。

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

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

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

是的,但它有一些缺点:

  • 从我的课程中派生出来的某个人可能会发布他的 ctor 并绕过安全措施;
  • 它会阻止一个人使用引用,一个人将不得不使用智能指针。
4

1 回答 1

9

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

不,在构造函数中调用虚方法会分派给派生最多的完整对象,也就是正在构造的对象。它不是被禁止的,它是明确定义的,它会做唯一有意义的事情。

不允许 C++ 基类知道最派生对象的身份,无论好坏。在该模型下,派生对象直到基本构造函数运行后才开始存在,因此没有什么可以获取类型信息。

在您的情况下,一些替代方案是:

  1. 从派生的构造函数调用 CreateChild。您已经需要定义CreateChild ,所以这是一个增量步骤。您可以protected: void init()在基类中添加一个函数来封装此类初始化。
  2. 使用 CRTP。将基类设为模板,让子类提供 DerivedType,然后以这种方式调用构造函数。这种设计有时可以完全消除虚函数。
  3. 使子指针成为构造函数参数,例如工厂函数。
  4. 在返回父对象之前,使用工厂函数模板生成适当类型的子指针。这消除了类模板的复杂性。使用类型特征模式对子类和父类进行分组。
于 2013-01-31T08:00:19.770 回答