【发布时间】:2013-01-31 07:49:01
【问题描述】:
禁止在 C++ 中调用虚方法,但在某些情况下它可能非常有用。
考虑以下情况 - 一对 Parent 和 Child 类。父构造函数需要实例化一个子类,因为它必须以特殊方式对其进行初始化。 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 ; 通过 SetWindowLongPtr 等引用this)
另一方面,派生类应该能够指定样式和扩展样式、窗口的类名等。
如果在 Control 的构造函数中构造窗口实例是要履行的合同,我认为除了在 ctor 中使用 VM 之外没有其他解决方案(这是行不通的)。
可能的解决方法:
- 使用静态多态性(例如,类 Derived : public Base),但如果想要从 Derived 派生,它将不起作用;
- 如果可能,将派生的 lambda 传递给基础 ctor - 它会起作用,但它是一个完全的核心解决方案;
- 将大量参数从派生的 ctor 传递到基 ctor - 它应该可以工作,但既不优雅,也不易于使用。
我个人不喜欢他们俩。出于好奇,我检查了问题是如何在 Delphi 的 VCL 中解决的,你知道什么,基类调用 CreateParams,它是虚拟的(Delphi 允许这样的调用和保证,它们是安全的 - 类字段在创建时初始化为 0 )。
如何克服这种语言限制?
编辑:回应答案:
从派生的构造函数调用 CreateChild。您已经需要定义 CreateChild,因此这是一个增量步骤。您可以在基类中添加 protected: void init() 函数来封装此类初始化。
它会起作用,但它不是一个选项 - 引用著名的 C++ 常见问题解答:
第一个变体最初是最简单的,尽管实际上想要创建对象的代码需要一点程序员自律,这实际上意味着你注定要失败。说真的,如果只有一两个地方真正创建了这个层次的对象,程序员的自律是相当本地化的,应该不会造成问题。
使用 CRTP。将基类设为模板,让子类提供 DerivedType,然后以这种方式调用构造函数。这种设计有时可以完全消除虚函数。
这不是一个选项,因为它只能工作一次,用于基类及其直接后代。
使子指针成为构造函数参数,例如工厂函数。
到目前为止,这是最好的解决方案 - 将代码注入基本 ctor。就我而言,我什至不需要这样做,因为我可以参数化基本 ctor 并从后代传递值。但它确实会起作用。
使用工厂函数模板在返回父级之前生成适当类型的子级指针。这消除了类模板的复杂性。使用类型特征模式对子类和父类进行分组。
是的,但它有一些缺点:
- 从我的课程派生的某人可能会发布他的 ctor 并绕过安全措施;
- 它会阻止使用引用,必须使用智能指针。
【问题讨论】:
-
它们不被禁止。它们只会让你的生活更加悲惨。
-
A 不完全同意悲惨的部分。首先,在给出的示例中,它们似乎是解决问题的唯一优雅方法。此外,如果虚拟方法符合静态(不使用类字段)的条件,那么调用这样的方法是绝对安全的。
-
关于禁止部分。我将示例修改为可编译的。运行后,它显示的是“Child”而不是“DerivedChild”,因此虚拟方法(虚拟,例如 DerivedParent::CreateChild())没有被调用。
-
这里有可以回答的问题吗? (问题通常以问号结尾
?) -
@abelenky 我已经收到了我的问题的答案,但我已经编辑了帖子,现在它包含了一个明确的问题。
标签: c++ constructor virtual-functions