您是否需要从所有派生类调用虚拟基类构造函数?即使他们不是最衍生的?

时间:2016-07-17 22:18:21

标签: c++ inheritance abstract-class multiple-inheritance diamond-problem

我遇到多重继承和钻石问题。

出现问题是因为我的基类构造函数需要一个参数。编译器尝试为我的两个抽象类生成默认构造函数,但由于默认构造函数无法确定基类的参数,因此失败了。

我不明白为什么我的抽象类正在调用基础构造函数。我认为最派生的类是调用虚基类构造函数的类。

以下是重现我正在谈论的内容的代码:

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)
                 ^

3 个答案:

答案 0 :(得分:4)

您的示例使用EDGclang进行编译,但不会使用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