【问题标题】:Do you need to call virtual base class constructor from all derived classes? Even if they're not the most derived?您是否需要从所有派生类中调用虚拟基类构造函数?即使它们不是最衍生的?
【发布时间】:2016-11-20 10:55:59
【问题描述】:

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

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

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

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

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

【问题讨论】:

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


    【解决方案1】:

    您的示例使用EDGclang 编译,但不能使用gcc 编译。我不确定代码是否应该按原样编译,因为抽象基类的构造函数似乎被声明为已删除:根据 12.1 [class.ctor] 第 4 段,第六个项目符号,默认的默认构造函数被声明为已删除,如果任何子对象都没有默认构造函数:

    ... 类 X 的默认默认构造函数在以下情况下定义为已删除: ...

    • ...
    • 任何可能构造的子对象,除了带有大括号或相等初始化器的非静态数据成员外,具有类类型 M(或其数组),并且 M 没有默认构造函数或重载决议 (13.3)应用于M 的默认构造函数会导致歧义或导致从默认默认构造函数中删除或无法访问的函数,或者
    • ...

    对于 virtual 基类的创建 virtual 基类没有特殊豁免,即,默认的默认构造函数将被删除。

    对于抽象类,显然没有必要从构造函数成员初始值设定项列表中调用虚基。至少,根据其注释,这就是 12.6.2 [class.base.init] 第 8 段所说的:

    在非委托构造函数中,如果给定的潜在构造子对象不是由 meminitializer-id 指定的(包括由于构造函数没有 ctorinitializer 而没有 mem-initializer-list 的情况),那么

    • 如果实体是一个非静态数据成员,它有一个大括号或相等初始化器,或者
    • 构造函数的类是一个联合 (9.5),并且该联合的其他变体成员没有由 mem-initializer-id 指定或
    • 构造函数的类不是联合体,如果实体是匿名联合体的成员,则该联合体中没有其他成员由 mem-initializer-id 指定,实体按照 8.5 中的规定进行初始化;
    • 否则,如果实体是匿名联合或变体成员 (9.5),则不执行初始化;
    • 否则,实体默认初始化 (8.5)。

    [ 注意:抽象类(10.4)永远不是最派生类,因此它的构造函数从不初始化虚拟基类,因此可以省略相应的 mem-initializers。 — 尾注] ...

    最衍生基础的相关部分在 12.6.2 第 7 段最后一句:

    ... mem-initializer-id 表示虚拟基类的 mem-initializer 在执行任何非派生类的构造函数期间将被忽略。

    【讨论】:

    • 它不会真正改变任何东西来引入阻止自动默认构造函数生成的东西。所有基础都必须以某种方式初始化。所以最后一段是可以的,但 IMO 的标准引用是无关紧要的。
    • 感谢您的额外报价。显然这在 C++03 中不存在,但在 C++11 中有一个更清晰的版本。嗯,今天学习了,谢谢。 :)
    • 哦,你的意思是由于删除了默认构造函数,例如ContractA,代码不应该编译。但是由于抽象类的特殊处理,定义一个默认的默认构造函数是微不足道的,不需要虚拟基的成员初始化器。这是一个正确的总结吗?
    • @Cheersandhth.-Alf:是的 - 已删除默认构造函数的引用文本似乎暗示如果未明确定义默认构造函数将被删除。其他文本似乎暗示对抽象基的默认构造函数有特殊处理。 EDG 和 clang 的开发人员往往比我更了解这些细节,而且两个编译器都接受原始代码。可能是我的推理太严谨了……
    【解决方案2】:

    不幸的是,C+03 语言没有让抽象类成为特例。构造函数调用,并且从 C++11 和更高版本开始,g++ 编译器(我的版本 5.1.0)并不认为它们对此有特殊意义。

    因此在实践中,对于可移植代码,必须初始化所有基类,就编译器所知

    如果您想让源代码的读者清楚这些是虚拟调用,那么您可以通过 cmets 来实现,或者更好,直接在源代码中使用 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();
    }
    

    【讨论】:

    • 那太糟糕了。就我而言,我只是给基类一个默认参数。它在这里对我很有效,因为无论如何大多数对象都将具有相同的参数。然后我可以根据需要重载参数。不是很好,但是“dummy constructor”路由有级联效应,需要我写很多无用的代码。
    • @user2445507:也许您可以使用“无效”值作为默认值,assert 不是那个值。
    【解决方案3】:

    自 C++11 以来,您的代码格式良好。不幸的是,您看到了 gcc 错误53878

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-09-27
      • 2019-04-19
      • 2017-02-10
      • 2014-11-27
      • 1970-01-01
      • 2011-05-03
      • 1970-01-01
      相关资源
      最近更新 更多