为什么C ++不允许派生类在初始化列表中使用基类成员?

时间:2017-07-09 04:11:14

标签: c++ list class initialization derived

我有以下计划:

#include<iostream> 
using namespace std; 
struct Base01{ 
    int m; 
    Base01():m(2){} 
    void p(){cout<<m<<endl;} 
}; 
struct Derived01:public Base01{ 
    Derived01():m(3){} 
}; 
struct Derived02:virtual public Base01{ 
    Derived01():m(4){} 
}; 
struct my: Derived01,Derived02{ 
    my():m(5){} 
}; 
int main(){ 
    return 0; 
} 

gcc / clang都报告编译错误。

我只想知道这里的语言设计考虑是什么,为什么派生类只能在初始化列表中调用基类ctor,但不能直接使用基类成员?

4 个答案:

答案 0 :(得分:8)

您在构造函数初始化程序列表中执行的操作是初始化。在对象的生命周期中,只需要一次。一般情况下,这就是启动对象生命周期的原因。

基类的构造函数(在派生类的构造函数正确变为活动状态之前完成工作)已经初始化了基类的所有直接子对象。它已经开始了他们的一生。如果您尝试从派生类的构造函数中伸入并初始化基类的直接子对象,那么这显然是同一对象的第二次初始化。这在C ++中是完全不可接受的。语言设计通常不允许您第二次初始化。

在您的情况下,相关子对象具有基本类型int,因此很难看到这种“重新初始化”的危害。但是考虑一些不那么微不足道的东西,比如std::string对象。你如何建议派生类应该“撤消并重做”已经由基类执行的初始化?虽然形式上可以正确地执行它,但构造函数初始化程序列表并不是为此目的。

在一般情况下,做类似的事情需要一个语言功能,允许用户告诉基类的构造函数的某些内容“请将你的这个子对象保留为未初始化,我将进入并在以后从派生类“。但是,C ++不为用户提供此类功能。虚拟基类初始化中存在一个模糊的类似特征,但它有一个非常特定(和不同)的目的。

答案 1 :(得分:3)

在C ++中执行此操作的正确方法是将该值传递给基类构造函数。您的Base01类需要一个额外的构造函数,它可以获取m的所需值。像这样:

struct Base01{ 
    int m; 
    Base01():m(2){}

    // Added this:
    Base01(int mVal) : m(mVal) {} 

    void p(){cout<<m<<endl;} 
}; 

struct Derived01:public Base01{ 
    Derived01() : Base01(3) {}   // Calling base constructor rather than
                                 // initializing base member
};

struct Derived02:virtual public Base01{ 
    Derived01() : Base01(4){}    // Same here
};

struct my: Derived01,Derived02{ 
    my(): Base01(5){}            // And here.
}; 

正如AnT所说,你不能初始化两次 - 但是你可以设置它,这样就可以通过上面的方式按照你想要的方式初始化事物。

答案 2 :(得分:2)

您当然可以在ctor-initializer列表中使用基类成员:

struct Base
{
    int x;
    Base(int x) : x(x) {}
};

struct Derived
{
    int y;
    Derived() : Base(7), y(x) {}
}

此处,基础成员x出现在派生成员y的初始化程序中;它的价值将被使用。

AnT做了一个非常好的工作,解释了为什么ctor-initializer列表不能用于(重新)初始化基础子对象的成员。

答案 3 :(得分:0)

基本语言设计考虑因素是关注点的分离(避免使基类依赖于其派生类),并且基类负责初始化自己的成员(以及它拥有的任何基础)。

一个相关的考虑因素是,在基类构造函数完成之前,基类的成员不存在 - 就派生类构造函数而言。如果派生类的初始化列表能够进入并初始化基类成员,则会产生两种可能的后果

  • 如果尚未调用基类构造函数,则当派生类尝试初始化它们时,其成员将不存在。
  • 如果已调用基类构造函数,则已初始化该成员。初始化(与重新初始化的分配不同)在对象的生命周期中发生一次,因此再次初始化它是没有意义的。

这些可能性在实践中确实没有意义,除非基类设计不当(例如,它的构造函数没有正确初始化其成员)。所需的机器所以它们可能有意义(例如,改变层次结构中基类构造的顺序,取决于派生类试图初始化的成员)将使编译器机制更复杂(例如,能够控制和跟踪基类成员的构造顺序,如果派生类应该选择到达,并且还意味着类的构造顺序将取决于派生类)。这将在派生类上引入基类行为(初始化的方式)的依赖性。

更简单的方法是让基类提供一个构造函数,该构造函数正确初始化有问题的成员,并使派生类构造函数在其初始化列表中调用该基类构造函数。所有上述(假设的)考虑都会消失。