对于许多类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
。
对于更理智的设计的建议将不胜感激。
答案 0 :(得分:2)
简答:
不要在构造函数中调用虚函数。这样做会让你陷入困境。
更长的回答:
声明C c
分步创建和构建class C
的实例。该对象首先构造为class A
对象,然后构造为class B
对象,最后构造为class C
对象。在调用B::B()
时,没有任何概念认为该对象最终将是class C
的实例。
调用B::B()
时,它会调用虚函数doSomething()
,在这种情况下调用B::doSomething()
。还没有问题;该功能存在。问题在于doSomethingHelper()
正文中对B::doSomething()
的调用。那个功能在这一点上是纯虚拟的。如上所述,没有迹象表明该对象最终将成为class C
的实例。无法调用无法调用的函数C::doSomethingHelper()
,无法从B::B()
或A::A()
调用。没有A::doSomethingHelper()
,没有B::doSomethingHelper()
,因此功能不存在。你是干杯。
我想知道是否有更好的设计。
有很多更好的设计。最简单的是不要从类B的构造函数中调用doSomething()
。将该调用移动到类C的构造函数。即使这样,从构造函数中调用虚函数也许不是一个好主意。如果D类继承自C类并覆盖C::doSomethingHelper()
怎么办?将通过调用C::doSomethingHelper()
而不是D::doSomethingHelper()
来构建D类的实例。
答案 1 :(得分:1)
根据标准:
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&#39的构造函数触发两阶段初始化:
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. Demo here的成员。
答案 2 :(得分:1)
您不能在构造函数中对派生类使用动态调度。
当B
的构造函数正在运行时,尚未创建C
子对象,因此不能使用其任何覆盖函数。由于B
没有为doSomethingHelper
提供实施,因此无法做任何明智的事。
答案 3 :(得分:0)
将所有复杂性移至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
初始化这些成员。