考虑以下示例代码:
#include <iostream>
using namespace std;
class base
{
public:
base()
{
cout << "ctor in base class\n";
}
};
class derived1 : public base
{
public:
derived1()
{
cout <<"ctor in derived class\n";
}
};
int main()
{
derived1 d1obj;
return 0;
}
问题
创建
d1obj
时,构造函数按派生顺序调用:先调用基类构造函数,然后调用派生类构造函数。这样做是因为以下原因吗:为了构造派生类对象,需要先构造基类对象
?d1obj
是否包含基类对象?
我再补充一个问题
3)创建d1obj时,控件先到达基类构造函数再到派生类构造函数?还是反过来:它首先到达派生类的构造函数,发现它有基类,因此控制权转到基类中的构造函数?
最佳答案
1) 是的,首先构造基类,然后构造非静态数据成员,然后调用派生类的构造函数。原因是此类的构造函数中的代码可以看到和使用完全构造的基。
2) 是的。您可以完全按字面理解:在分配给派生类对象的内存中,有一个区域称为“基类子对象”。派生类对象“包含”基类子对象的方式与它包含任何非静态数据成员的成员子对象的方式完全相同。实际上,问题中给出的示例恰好是一个特例:“空基类优化”。此基类子对象允许大小为零,即使 base
类型的完整对象永远不会大小为零。
不过,这种收容是一种低级的东西。正如其他人所说,概念上基与成员不同,语言的语法和语义对它们的处理方式不同,即使子对象本身只是类布局的一部分。
3) 这是一个实现细节。基类构造函数主体中的代码在派生类构造函数主体中的代码之前执行,实际上派生类构造函数然后在一个不可见的编译器生成的 try/catch block 中执行,以确保如果它抛出,基类被破坏。但这取决于编译器如何根据发出的代码中的函数入口点实际执行的操作来实现这一点。
当一个类具有虚基类时,构造函数通常会导致发出两个不同的函数体 - 一个用于此类是派生程度最高的类型时使用,另一个用于此类本身是基类时使用。原因是虚基类是由最派生的类构造的,以确保它们在共享时只构造一次。因此构造函数的第一个版本将调用所有基类构造函数,而第二个版本将只调用非虚基类的构造函数。
编译器总是“知道”类有什么基,因为你只能构造一个完整类型的对象,这意味着编译器可以看到类定义,并指定基。所以不存在只有在输入构造函数时“发现它有一个基类”的问题——编译器知道它有一个基类,如果对基类构造函数的调用位于内部派生类的构造函数代码,那只是为了编译器的方便。它可以在您构造对象的每个地方发出对基类构造函数的调用,就此而言,在派生类构造函数可以并且是内联的情况下,这就是最终效果。
关于c++ - 派生类对象是否包含基类对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8418471/