我遇到多重继承和钻石问题。
出现问题是因为我的基类构造函数需要一个参数。编译器尝试为我的两个抽象类生成默认构造函数,但由于默认构造函数无法确定基类的参数,因此失败了。
我不明白为什么我的抽象类正在调用基础构造函数。我认为最派生的类是调用虚基类构造函数的类。
以下是重现我正在谈论的内容的代码:
class VirtualBase
{
public:
VirtualBase(int initial) :
count(initial)
{}
int getCount() const
{
return count;
}
void increment()
{
count++;
}
private:
int count;
};
class ContractA : public virtual VirtualBase
{
public:
virtual void doSomething() = 0;
};
class ContractB : public virtual VirtualBase
{
public:
virtual void doSomethingElse() = 0;
};
class Concrete : public ContractA, public ContractB
{
public:
Concrete() :
VirtualBase(0)
{}
virtual void doSomething()
{
increment();
}
virtual void doSomethingElse()
{
// etc...
}
};
int main()
{
Concrete concrete;
concrete.doSomething();
concrete.doSomethingElse();
return 0;
}
我收到以下错误(对于每个合同):
main.cpp: In constructor ‘ContractA::ContractA()’:
main.cpp:29:7: error: no matching function for call to ‘VirtualBase::VirtualBase()’
class ContractA : public virtual VirtualBase
^
main.cpp:29:7: note: candidates are:
main.cpp:9:3: note: VirtualBase::VirtualBase(int)
VirtualBase(int initial) :
^
main.cpp:9:3: note: candidate expects 1 argument, 0 provided
main.cpp:4:7: note: VirtualBase::VirtualBase(const VirtualBase&)
class VirtualBase
^
main.cpp:4:7: note: candidate expects 1 argument, 0 provided
main.cpp: In constructor ‘Concrete::Concrete()’:
main.cpp:53:17: note: synthesized method ‘ContractA::ContractA()’ first required here
VirtualBase(0)
^
答案 0 :(得分:4)
您的示例使用EDG和clang进行编译,但不会使用gcc进行编译。我不确定代码是否应该按原样编译,因为似乎抽象基类的构造函数被声明为已删除:根据12.1 [class.ctor]第4段,第6个子弹,默认的默认构造函数声明为如果任何子对象没有默认构造函数,则删除:
...如果符合以下情况,则将类X的默认默认构造函数定义为已删除: ...
- ...
- 任何可能构造的子对象,除了具有大括号或等号初始化器的非静态数据成员外,具有类型
M
(或其数组),并且M
没有默认构造函数或超载应用于M
的默认构造函数的分辨率(13.3)导致歧义或在默认构造函数中删除或无法访问的函数,或- ...
对于virtual
基础的创建virtual
基础的类没有特殊豁免,即默认的默认构造函数将被删除。
对于抽象类,显然没有必要从构造函数成员初始化列表中调用虚拟基础。至少,这就是12.6.2 [class.base.init]第8段根据其说明所说的:
在非委托构造函数中,如果给定的可能构造的子对象不是由meminitializer-id指定的(包括没有mem-initializer-list的情况,因为构造函数没有ctorinitializer),那么
- 如果实体是非静态数据成员,该成员具有支撑或等于初始化器并且
- 构造函数的类是一个union(9.5),并且该联合的其他变体成员没有由mem-initializer-id或
指定- 构造函数的类不是联合,并且,如果实体是匿名联合的成员,则该联合的其他成员不是由mem-initializer-id指定的,则实体按照8.5中的规定进行初始化; < / LI>
- 否则,如果实体是匿名联合或变体成员(9.5),则不执行初始化;
- 否则,实体默认初始化(8.5)。
[注意:抽象类(10.4)永远不是最派生的类,因此它的构造函数永远不会初始化虚拟基类,因此可以省略相应的mem-initializer。 - 结束说明] ......
最衍生基础的相关部分在12.6.2第7段,最后一句:
... mem-initializer,其中mem-initializer-id表示虚拟基类,在执行任何不是派生类最多的类的构造函数时会被忽略。
答案 1 :(得分:3)
不幸的是,C + 03语言没有使抽象类成为特殊情况。构造函数调用,从C ++ 11及更高版本开始,g ++编译器(我的版本5.1.0)并不认为它们是特殊的。
因此在实践中,对于可移植代码,必须初始化所有基础,就编译器知道而言。
如果您想向读者清楚地知道这些是虚拟调用,那么您可以通过注释来实现,或者更好,直接在源代码中使用assert
表示:
#include <assert.h>
struct Dummy_call {};
class VirtualBase
{
private:
int count;
protected:
VirtualBase( Dummy_call ) { assert( false ); }
public:
auto getCount() const -> int {
return count;
}
void increment() {
++count;
}
VirtualBase( int const initial )
: count(initial)
{}
};
class ContractA
: public virtual VirtualBase
{
public:
virtual void doSomething() = 0;
ContractA(): VirtualBase( Dummy_call{} ) {}
};
class ContractB
: public virtual VirtualBase
{
public:
virtual void doSomethingElse() = 0;
ContractB(): VirtualBase( Dummy_call{} ) {}
};
class Concrete
: public ContractA
, public ContractB
{
public:
void doSomething() override {
increment();
}
void doSomethingElse() override {} // etc...
Concrete()
: VirtualBase{ 0 }
{}
};
int main()
{
Concrete concrete;
concrete.doSomething();
concrete.doSomethingElse();
}
答案 2 :(得分:1)
自C ++ 11以来,您的代码格式正确。不幸的是,你看到了gcc bug 53878。