C ++ - 初始化和解构如何工作?

时间:2017-10-30 19:59:55

标签: c++ destructor

我编写了以下代码(这些类是用单独的.h文件编写的):

class A
{

public:
    A(){
        cout << "This is the constructor of A!" << endl;
        foo();
    }
    virtual ~A(){
        cout << "Destroyed A type" << endl;
    }

    virtual void foo();
};

void A::foo()
{
    cout << "foo()::A" << endl;
}

class B: public A
{

public:
    B(){}
    ~B(){
        cout << "Destroyed B type" << endl;
    }

    void foo();
};

void B::foo()
{
    cout << "foo()::B" << endl;
}

以下主要功能:

int main()
{
    A b = B();
    A *bp = &b;
    A &ra = b;

    bp->foo();
    ra.foo();

    return 0;
}

当我运行它时,我得到以下输出:

This is the constructor of A!
foo()::A
Destroyed B type
Destroyed A type
foo()::A
foo()::A
Destroyed A type

我的问题是 - 为什么B()会被立即销毁?不是假设指向它,是一个B型对象?如何将b初始化为新B?也许我对Java感到困惑,我们会说b的静态类型是A而动态类型是B.这不是同样的情况吗?我很想了解这段代码背后的机制。

3 个答案:

答案 0 :(得分:3)

  

为什么B()会被立即销毁?

在这一行:

A b = B();

你做了几件事。以下是步骤:

  • 您创建了一个B类型的临时值,它调用默认的基类构造函数,该构造函数输出:

    This is the constructor of A!
    foo()::A
    

    然后调用B

  • 的默认构造函数
  • 之后,创建类型为A的对象,并调用编译器生成的带有A参数(A(const A& a))的复制构造函数,并对临时值进行切片。 (顺便说一句,你应该遵循the rule of three

  • 然后,临时值被销毁,因为它是B类型的值,它首先调用B析构函数,然后是析构函数,这就是你得到它的原因:

    Destroyed B type
    Destroyed A type
    
  

不想指向它,是B型对象吗?

不,一点也不。 b不是指向A对象的指针,它是A值类型。

  

如何将b初始化为新B

它是一种值类型,因此您无法成为&#34; B类型&#34;,因为对象切片。如果您不知道什么是对象切片,您应该阅读:What is object slicing ?

  

也许我对Java感到困惑

  

我们会说b的静态类型是A而动态类型是B.这不是同样的情况吗?

在java中,它是。在C ++中,如果你想做同样的事情,你需要这样做:

A* b = new B();

这里,b的静态类型是A,但它指向B对象。当然,在使用它之后不要忘记释放内存,如下所示:

delete b;

因为C ++不会为你管理内存。或者更好:使用智能指针。

  

我还有另一个问题:这一行会发生什么:&#34; A&amp; ra = b;&#34;?

创建名为A的{​​{1}}引用类型,并引用对象ra

  

ra究竟是什么?

b是A. See there

的引用类型
  

我知道它的类型是A但幕后会发生什么?例如,当我尝试&#34; A a = b;&#34;一个对象被创建了。 ra不是这种情况,但ra是一个A对象(例如,如果我们将使foo()不是虚拟的,它将选择A的foo)。你知道解释一下吗?

不,不,不。 ra类型是ra的引用类型,而不是A值类型

引用类型基本上是别名,是值的另一个名称。如this tutorial中所述:

  

C ++引用允许您为变量创建第二个名称,您可以使用该名称来读取或修改存储在该变量中的原始数据。虽然这一开始可能听起来不太吸引人,但这意味着当您声明一个引用并为其赋值变量时,它将允许您将引用视为原始变量,以便访问和修改该值原始变量 - 即使第二个名称(引用)位于不同的范围内。这意味着,例如,如果你使函数参数引用,你将有效地改变传递给函数的原始数据。


在您的情况下,A是引用对象ra的引用类型,因此b是对象ra的另一个名称。因此,当您执行b时,ra.foo()完全相同,因为b.foo()引用了对象ra,引用了相同的数据存储器中。

在幕后,它通常使用指针(see this SO answer)实现。也许在你的情况下,它可以简单地删除(我不知道,我试图查看汇编文件,但它很难阅读)

答案 1 :(得分:2)

B()创建类型为B的对象,A b = B()然后将创建的对象复制到变量b中。此时存在2个对象,一个类型为B的临时对象在复制后被删除,另一个对象类型为A,因为在复制到{{{{}}}之后,您的B对象被切片1}}。

答案 2 :(得分:1)

编译此源时,编译器会自动为AB类生成复制构造函数运算符。执行A b = B();时,会发生以下情况:

  1. B的临时实例由构造函数B::B()创建。
  2. 调用A的自动生成的复制构造函数。这将从步骤1生成的临时实例b构造实例B
  3. 销毁在步骤1中创建的临时对象。 (这是调用B::~B()的地方)