禁止在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之外没有其他解决方案(什么是行不通的)。
可能的解决方法:
我个人不喜欢他们。出于好奇,我查了一下,如何在Delphi的VCL中解决问题,你知道什么,基类调用CreateParams,它是虚拟的(Delphi允许这样的调用和保证,它们是安全的 - 类字段在创建时初始化为0 )。
如何克服这种语言限制?
编辑:回答答案:
从派生构造函数中调用CreateChild。您已经要求定义CreateChild,因此这是一个增量步骤。您可以在基类中添加protected:void init()函数以保持此类初始化的封装。
它会起作用,但它不是一个选项 - 引用着名的C ++ FAQ:
第一个变体最初是最简单的,尽管实际上想要创建对象的代码需要一点点程序员自律,这在实践中意味着你注定要失败。说真的,如果只有一两个地方实际上创建了这个层次结构的对象,那么程序员自律是非常本地化的,不会引起问题。
使用CRTP。使基类成为模板,让子类提供DerivedType,并以这种方式调用构造函数。这种设计有时可以完全消除虚函数。
这不是一个选项,因为它只对基类及其直系后代起作用一次。
使子指针成为构造函数参数,例如工厂函数。
到目前为止,这是最好的解决方案 - 将代码注入基础ctor。在我的情况下,我甚至不需要这样做,因为我可以参数化基本ctor并传递后代的值。但它确实会奏效。
使用工厂函数模板在返回父级之前生成相应类型的子指针。这消除了类模板的复杂性。使用类型特征模式对子类和父类进行分组。
是的,但它有一些缺点:
答案 0 :(得分:9)
禁止在C ++中调用虚方法,但有一些情况可能非常有用。
不,在构造函数中调用虚方法会调度到派生最多的完整对象,这是正在构建的对象。它不是被禁止的,它是明确定义的,它是唯一有意义的东西。
不管是好还是坏,都不允许C ++基类知道最派生对象的身份。在模型下,派生对象在基础构造函数运行之后才开始存在,因此没有任何内容可以获取类型信息。
您的案例中有一些替代方案是:
protected: void init()
函数以保持此类初始化的封装。