【问题标题】:C++ - How does initializing and desctruction works?C++ - 初始化和销毁​​如何工作?
【发布时间】:2017-10-30 19:59:55
【问题描述】:

我编写了以下代码(这些类是在单独的 .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 初始化为新的 B?也许我对Java感到困惑,我们会说b的静态类型是A,动态类型是B。这不是同样的情况吗?我很想了解这段代码背后的机制。

【问题讨论】:

  • 也许我对 Java 感到困惑 - 你敢打赌!!
  • A b = B(); 这行代码创建了一个对象。而=右边的东西也创建了一个对象。
  • 我强烈建议在 StackOverflow 中搜索“C++ 初始化构造函数析构函数”,因为已经有大量关于此主题的帖子。
  • 试着向你的rubber duck解释这一行:A b = B();
  • 能否在 B 构造函数中添加一些跟踪语句来查看 A b = B();创建一个A类型的类,调用一个B类型的类,然后销毁B,然后销毁A?

标签: c++ destructor


【解决方案1】:

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

在这一行:

A b = B();

你做了几件事。步骤如下:

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

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

    然后调用B的默认构造函数

  • 之后,创建A 类型的对象,并调用编译器生成的带有A 参数(A(const A&amp; a))的复制构造函数,并对临时值进行切片。 (对了,你应该关注the rule of three

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

    Destroyed B type
    Destroyed A type
    

b不应该指向它,是一个B类型的对象吗?

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

如何将 b 初始化为新的 B

它是一个值类型,所以你不能是“B 类型”,因为对象切片。如果您不知道什么是对象切片,请阅读:What is object slicing ?

也许我对 Java 感到困惑

是的。

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

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

A* b = new B();

这里,b的静态类型是A,但是它指向一个B对象。当然,别忘了用完后释放内存,像这样:

delete b;

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

我还有一个问题:“A &ra = b;”这一行发生了什么?

创建一个名为raA 引用类型,并引用对象b

究竟什么是ra?

ra 是 A 的引用类型。See there

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

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

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

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


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

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

【讨论】:

  • 谢谢 :),我将阅读切片并查找复制构造函数和智能指针
  • 我还有一个问题:“A &ra = b;”这一行发生了什么? ra 究竟是什么?我知道它的类型是 A,但幕后发生了什么?例如,当我尝试“A a = b;”时创建了一个 A 对象。这不是 ra 的情况,但是 ra 是一个 A 对象(例如,如果我们使 foo() 不是虚拟的,它将选择 A 的 foo)。你知道解释这个吗? (我搜索了但不知道如何找到类似的例子)
【解决方案2】:

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

【讨论】:

  • 不是B 的两个实例。该副本是A 的一个实例。
  • 没错,B 实例被分割成A 实例。
  • 为什么会被切片?以及为什么后来它被视为 A 类型而不是 B 类型(如您所见,仅调用了 A 的 foo() 方法)?
  • 如果使用A b,您会在堆栈上保留空间来存储A 类型的对象。但是,当您将B 类型的对象复制到其中时,所有不属于A 类型的对象都会被截断。你想要实现的是多态性,你可以用指针来做。 A* b = new B() 将在堆上分配B 类型的对象,然后将地址存储在b 中。现在什么都没有被截断,你的 A* 指向一个 B 对象。但是不要忘记在使用完对象后delete。我建议看看 pointers 的工作原理以及 smartpointers 是什么。
【解决方案3】:

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

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

【讨论】:

  • 这里没有作业。
  • @juanchopanza 是对的。没有调用赋值运算符。调用的是 A 的复制构造函数
猜你喜欢
  • 2012-10-14
  • 1970-01-01
  • 2015-07-18
  • 2023-03-30
  • 1970-01-01
  • 2013-08-13
  • 2020-09-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多