[英]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()
怎么办? 将通过调用C::doSomethingHelper()
而不是D::doSomethingHelper()
来构造D类的实例。
根据标准:
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
初始化。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.