派生类对象是否包含基类对象?

时间:2011-12-07 16:09:46

标签: c++ constructor derived-class base-class

请考虑以下示例代码:

#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;
}

问题

  1. 创建d1obj时,将按推导顺序调用构造函数:首先调用基类构造函数,然后调用派生类构造函数。这样做是因为以下原因:In-order to construct the derived class object the base class object needs to be constructed first

  2. d1obj是否包含基类对象?

  3. 我还要再添加一个问题

    3)当创建d1obj时,控件首先到达基类构造函数然后转到派生类构造函数?或者它是相反的方式:它首先到达派生类构造函数,发现它有基类,所以控件转到基类的构造函数?

5 个答案:

答案 0 :(得分:6)

1)是的,首先构造base,然后构造非静态数据成员,然后调用派生类的构造函数。原因是这个类的构造函数中的代码可以看到并使用一个完整构造的基础。

2)是的。您可以完全按字面意思理解:在分配给派生类对象的内存中,有一个称为“基类子对象”的区域。派生类对象“包含”基类子对象,其方式与包含任何非静态数据成员的成员子对象的方式完全相同。实际上,问题中给出的例子恰好是一个特例:“空基类优化”。允许此基类子对象的大小为零,即使类型为base的完整对象的大小为零。

但是,这个遏制是一个低级别的东西。这是正确的,因为其他人说概念基础与成员不同,语言的语法和语义对待它们的方式不同,即使子对象本身只是类的布局的一部分。 / p>

3)这是一个实现细节。基类构造函数体中的代码在派生类构造函数体内的代码之前执行,实际上派生类构造函数然后在不可见的编译器生成的try / catch块中执行,以确保它是否抛出,基类被破坏。但是编译器如何根据发出的代码中的函数入口点实际执行此操作来实现这一点。

当一个类具有虚拟基础时,构造函数通常会导致发出两个不同的函数体 - 一个在此类是派生类型最多时使用,另一个在此类本身是基类时使用。原因是虚拟基类是由派生程度最高的类构造的,以确保在共享它们时它们只构造一次。因此构造函数的第一个版本将调用所有基类构造函数,而第二个版本将仅调用非虚拟基础构造函数。

编译器总是“知道”该类具有什么基础,因为您只能构造一个完整类型的对象,这意味着编译器可以看到类定义,并指定基类。因此,当输入构造函数时,只有“发现它有一个基类”是没有问题的 - 编译器知道它有一个基类,并且如果对基类构造函数的调用位于里面派生类构造函数代码,这只是为了方便编译器。它可以在你构造一个对象的每个地方发出对基类构造函数的调用,对于这种情况,在派生类构造函数可以被内联的情况下,这就是最终效果。

答案 1 :(得分:1)

  1. 是。想象一下,在派生类的构造函数中,您希望使用基类的某些成员。因此,需要对它们进行初始化。因此,首先调用基类构造函数是有意义的。

  2. d1obj 基类对象。那是什么继承。在某种程度上,你可以说它包含基类的对象。在内存中,对象的第一部分将对应一个基础对象(在您的示例中,您没有虚函数,如果您这样做,您指的是vftable的{​​{1}}首先,然后是你的基类成员),然后是属于derived1的成员。

答案 2 :(得分:1)

是的,是的。


自编辑以来,广告3)派生类构造函数调用基类构造函数作为其第一个任务行为; 然后所有成员对象构造函数,最后它执行构造函数体。

销毁以相反的方式工作:首先执行析构函数体,然后销毁成员对象(按其破坏的相反顺序),最后调用基础子对象析构函数(这就是为什么你总是需要一个可访问的析构函数)基类,即使它是纯虚拟的。)

答案 3 :(得分:0)

1--是的。这样做是合乎逻辑的。

derived1类型的对象是base类型的特殊对象,这意味着它们首先是base类型的对象。这是首先构造的,然后“derived1”将其“特殊性”添加到对象中。

2--这不是遏制的问题,而是继承。请参阅上面的段落以更好地理解这个答案。

答案 4 :(得分:0)

  1. 嗯,从概念上讲,并非如此。 d1obj包含base实例所有的所有数据成员,并响应所述实例所有但不“包含”base实例的所有成员函数:你不能说d1obj.base.func()。如果您已经重载了父级声明的方法,则可以调用d1obj.base::func()来获取其实现,而不是仅仅调用d1obj->func()

  2. 虽然看起来有点像分裂你说包含你父母的所有数据和方法而没有概念性地包含你父母的实例,但这是一个重要的区别,因为你经常可以获得许多好处通过创建一个包含“父”类作为成员数据的类来直接继承,如下所示:

    class derived2 /*no parent listed */ {
    public:
    
       derived2() :_b() {}
    
    private:
        base _b;
    }
    

    这样的构造允许您使用base已经实现的方法,作为您自己的方法的实现细节,而不必暴露任何base声明公开但不公开的方法想要授予访问权限。一个重要的例子是stack,它可以包含另一个STL容器的实例(例如vectordequelist),然后使用他们的back()top()实现push_back()push()pop_back()实现pop(),无需向用户公开原始方法。