中级类:调用的纯虚拟方法
intermediate class: pure virtual method called
对于许多类C1
、C2
等,初始化看起来是相等的,只是类与类之间略有不同。 因此,我创建了一个承载初始化的基类B
,例如:
class B {
public:
B()
{
// complex initializations
int a = doSomething();
// more complex stuff with `a`
};
virtual int doSomething()
{return 2 * doSomethingHelper();}
protected:
virtual int doSomethingHelper() = 0;
};
class C: public B {
protected:
virtual int doSomethingHelper()
{return 1;}
};
int main() {
C c;
return 0;
}
此代码失败,并显示
pure virtual method called
terminate called without an active exception
Aborted (core dumped)
由于doSomethingHelper()
用于初始化B
.
我想知道是否有更好的设计。我的目标是:
使
C
的用户界面尽可能简单:C
没有构造函数参数。使
C
本身尽可能简约,以便B
的其他具体化都很短。理想情况下,它只包含doSomethingHelper
.
建议采用更理智的设计将不胜感激。
简短回答:
不要从构造函数中调用虚函数。这样做会给你带来麻烦。
更长的答案:
声明C c
分步骤创建和构造class C
的实例。该对象首先构造为class A
对象,然后构造为class B
对象,最后构造为class C
对象。在调用B::B()
时,没有概念认为该对象最终将成为class C
的实例。
当调用B::B()
时,它会调用虚函数doSomething()
,在这种情况下意味着调用B::doSomething()
。还没有问题;该函数存在。问题在于B::doSomething()
体内对doSomethingHelper()
的呼吁。该函数在这一点上是纯虚拟的。如上所述,没有迹象表明此对象最终将成为class C
的实例。无法调用的函数C::doSomethingHelper()
不能从B::B()
或A::A()
调用。没有A::doSomethingHelper()
,没有B::doSomethingHelper()
,所以函数不存在。你干杯。
我想知道是否有更好的设计。
有很多更好的设计。最简单的方法是不要从类 B 的构造函数中调用doSomething()
。 将该调用移动到类 C 的构造函数。即便如此,从构造函数中调用虚函数可能不是一个好主意。如果类 D 继承自类 C 并覆盖C::doSomethingHelper()
怎么办?类 D 的实例将通过调用 C::doSomethingHelper()
而不是 D::doSomethingHelper()
来构造。
根据标准:
10.4/6:成员函数可以从抽象类的构造函数(或析构函数(调用;进行虚拟调用的效果(10.3(对一个纯虚函数直接或间接地用于从此类构造函数创建(或销毁(的对象(或析构函数(未定义
这是因为,当您构造 C 时:
-
首先,使用 B(( 构造函数构造子对象 B。 那时,使用的仍然是 B 的虚函数。 您收到错误是因为此时尚未为其定义doSomethingHelper((。
-
只有当 B(( 完成时,虚拟将 C 的虚函数变为活动状态。
两阶段初始化
这种情况只能通过两阶段初始化来避免:首先构造,然后调用初始化函数。 没有你想要的那么好和用户友好。
class B {
public:
B() { /* complex initializations */ }
...
protected:
void init() { // the rest of the what you wanted in constructor
int a = doSomething();
// more complex stuff with `a`
}
};
然后可以通过 C 的构造函数触发两个阶段初始化:
class C {
public:
C() : B() { // first B is constructed
init(); // then the body of C's constructor is executed
}
...
};
两阶段初始化的变体
您可以使用一个小变体来隐藏两阶段方法,并让用户在定义或不定义自己的构造函数方面有更多的自由。
在 B 中定义一个辅助嵌套类:
protected:
class initialiser {
public:
initialiser(B*b) {b->init();} // the constructor launches the second phase init
};
在 C 中,你只需要添加一个受保护的成员变量:
class C: public B {
...
protected:
B::initialiser bi{this}; // this triggers automaticcaly the second phase
...
};
该标准的规则确保首先构造B,然后构造C的成员。 在这里演示。
对构造函数中的派生类使用动态调度。
当 B
的构造函数运行时,尚未创建 C
子对象,因此不能使用其任何重写函数。由于B
没有为doSomethingHelper
提供实现,因此无法做任何明智的事情。
将所有复杂性移到B::doSomething
中,并从继承链的末端调用该方法,C
:
class B {
public:
B()
{};
virtual int doSomething()
{
// complex initializations
int a = 2 * doSomethingHelper();
// more complex stuff with `a`
return a;
}
protected:
virtual int doSomethingHelper() = 0;
};
class C: public B {
public:
C(): B()
{
int a = doSomething();
}
protected:
virtual int doSomethingHelper()
{return 1;}
};
int main() {
C c;
return 0;
}
这可能需要您将以前B
private
成员中的一些protected
,以便可以通过 C
初始化它们。
- 在模板基类中为继承类中的可选重写生成虚拟方法
- 跨 DLL 边界访问虚拟方法是否安全/可能?
- 是否可以使用基类非虚拟方法中的派生类虚拟方法?
- 如何编写 operator= 用于使用虚拟方法与非平凡成员的匿名联合
- 让编译器告诉什么确切的纯虚拟方法使结构抽象?
- 使用模板而不是虚拟方法的管道模式
- 派生类调用父类的方法,该方法调用重写的虚拟方法调用错误的方法
- 为什么调用没有正文的纯虚拟方法不会导致链接器错误?
- 出于什么目的,非虚拟方法将与C++一起使用?
- 为什么使用存储在虚拟方法表中的地址调用虚拟函数的函数会返回垃圾?
- 如何重写继承的嵌套类中存在的虚拟方法
- 私有虚拟方法有什么用?
- 基类可以声明虚拟方法但不定义它吗?仍然在派生类中定义
- googletest:测试基类具有纯虚拟方法的派生类时的核心转储
- 确保模拟的 GTest 方法覆盖虚拟方法
- CPP 继承虚拟方法解析顺序
- 我是否应该在包含虚拟方法的类上使用'memcpy'?如果没有,如何替换它?
- 用c++中的纯虚拟方法抽象模板类
- 解决虚拟方法的歧义继承的两种方法
- 没有针对完全专用模板类的外联虚拟方法定义