在C++中的多重继承中调用构造函数中的基成员

Calling a base member in constructor in multiple inheritance in C++

本文关键字:构造函数 成员 多重继承 C++ 调用      更新时间:2023-10-16

假设我有这两个类

class base_size
{
public:
   int size()
   { return 5; }
};
class base_implement
{
public:
   base_implement(int s) : _vec(s)
   {
      cout << "size : " << _vec.size() << endl;
   }
private:
   vector<float> _vec;
};

如果我从这两个类中继承,那么在另一个类的构造函数中调用其中一个类成员函数可以吗?例如

class derived : 
   public base_implement,
   public base_size
{
public:
   derived() : base_size(), base_implement(size())
   {
      // Is this OK? 
      // If derived is not yet constructed can I access this->size() ?
      // Works in VC++. Not sure about other compilers.
   }
};

原则上没关系。基子对象和成员对象是在派生构造函数体运行之前构造的,因此可以毫无问题地调用成员函数。您甚至可以在构造函数中调用自己的成员函数;您只需要确保它们不依赖于稍后在同一构造函数中出现的任何内容。

只需确保按正确的顺序(即其声明的顺序)调用基初始值设定项,和/或修复类定义:对于base_size::size(),您希望base_size子对象是完全构造的,因此它必须放在第一位。

 class derived : base_size, base_implement
 {
     derived() : base_size(), base_implement(size()) { /* ... */ }
     // ...
 };

您可以安全地从初始化列表中调用继承的成员函数,前提是该函数的作用不取决于该继承级别的成员数据是如何初始化的。而且,由于size()不依赖于任何成员数据,它所做的只是返回一个文本

int size()
{ return 5; }

您的代码可以与任何编译器配合使用。因此,在初始化列表中甚至不需要base_size()

derived() : base_size(), base_implement(size())

在这种情况下。

然而,切换到一个更现实的例子,其中base_size有一个初始化实例变量(即成员数据)的构造函数,在初始化列表中有base_size()会更有意义:

class base_size
{
public:
   base_size ()
   { _size = 5; } // initialization
   int size()
   { return _size; }
private:
    int _size; // instance variable
};
class base_implement
{
public:
   base_implement(int s) : _vec(s)
   {
      cout << "size : " << _vec.size() << endl;
   }
private:
   vector<float> _vec;
};
class derived :
   public base_implement,
   public base_size
{
public:
   derived() : base_size(), base_implement(size())
   {
       // crash
   }
};

而且,在这个特定的例子中,程序会崩溃,因为vector不会接收到分配其大小的有效值。原因可能是在所谓的基说明符列表:中基类的顺序

public base_implement,
public base_size

关于权威机构,这是标准在第12.6.2节中所规定的,";初始化基和成员";

初始化应按以下顺序进行:

  • 首先,仅对于下面描述的最派生类的构造函数,虚拟基类应按照它们在基类的有向无环图的深度优先从左到右遍历中出现的顺序进行初始化,其中"从左到左"是派生类基类说明符列表中基类名称的出现顺序
  • 然后,直接基类应按照它们出现在基说明符列表中的声明顺序进行初始化(无论mem初始化器的顺序如何)
  • 然后,非静态数据成员应按照它们在类定义中声明的顺序进行初始化(同样,与mem初始化程序的顺序无关)
  • 最后,构造函数的主体被执行

所以如果你要更换

public base_implement,
public base_size

带有

public base_size,
public base_implement

这一切都将正确初始化,程序将在大多数符合标准的编译器中运行良好。首先,我刚刚测试过MSVC 10.0。