为什么析构函数在删除对象之前运行

时间:2016-12-07 18:47:12

标签: c++ destructor

当我运行此代码时,析构函数在删除对象之前启动。

代码在这里:

#include <string>
#include <vector>

using namespace std;

class Testi {
public:

    string name;
    Testi(string a) : name(a) {
        cout << "Im alive: " << name << endl;
    }
    ~Testi() {
        cout << "Im no longer alive: " << name << endl;
    }

};
int main() {

    vector <Testi> a;

    a.push_back(Testi("John"));
    a.push_back(Testi("Jack"));
    a.push_back(Testi("Jake"));

    cout << a[1].name;

    cin.get();
    return 0;
}

当我运行程序输出时:

  

我活着:约翰
  我不再活着:约翰
  我还活着:杰克   我不再活着:约翰
  我不再活着:杰克   我活着:杰克
  我不再活着:约翰
  我不再活着:杰克   我不再活着:杰克

     杰克

输入后:

  

我不再活着:约翰
  我不再活着:杰克   我不再活着:杰克

所以在每个push_back()之后所有析构函数都会运行。输出操作很好,因此对象仍然存在。 对于第一个析构函数运行4次!为什么呢?

4 个答案:

答案 0 :(得分:4)

以下是相关的代码:

vector <Testi> a;
a.push_back(Testi("John"));
  1. Testi("John")创建一个新的临时Testi对象。
  2. push_back将该对象复制到矢量中。
  3. 然后删除临时对象。
  4. 因此,意外的构造函数和析构函数调用来自临时的创建和删除。您可以使用emplace_back来避免额外的临时和副本,{{1}}将直接在向量中构造对象。

答案 1 :(得分:2)

为了更清楚地添加类的复制构造函数,例如以下方式,如本演示程序中所示

#include <iostream>
#include <vector>
#include <string>

using namespace std;

class Testi {
public:

    string name;
    Testi(const string &a) : name(a) {
        cout << "Im alive: " << name << endl;
    }
    Testi( const Testi &t ) : name(t.name + "_copy") {
        cout << "Im alive: " << name << endl;
    }
    ~Testi() {
        cout << "Im no longer alive: " << name << endl;
    }

};


int main()
{
    {
        vector <Testi> a;

        a.push_back(Testi("John"));
        a.push_back(Testi("Jack"));
        a.push_back(Testi("Jake"));

        cout << "---------------------" << endl;

        for (const auto &item : a) cout << item.name << ' ';
        cout << endl << endl;
    }
    {
        cout << "---------------------" << endl;
        vector <Testi> a;
        a.reserve(3);

        a.emplace_back("John");
        a.emplace_back("Jack");
        a.emplace_back("Jake");

        cout << "---------------------" << endl;

        for (const auto &item : a) cout << item.name << ' ';
        cout << endl << endl;
    }

    return 0;
}

它的输出可能看起来像

Im alive: John
Im alive: John_copy
Im no longer alive: John
Im alive: Jack
Im alive: John_copy_copy
Im no longer alive: John_copy
Im alive: Jack_copy
Im no longer alive: Jack
Im alive: Jake
Im alive: John_copy_copy_copy
Im alive: Jack_copy_copy
Im no longer alive: John_copy_copy
Im no longer alive: Jack_copy
Im alive: Jake_copy
Im no longer alive: Jake
---------------------
John_copy_copy_copy Jack_copy_copy Jake_copy

Im no longer alive: John_copy_copy_copy
Im no longer alive: Jack_copy_copy
Im no longer alive: Jake_copy
---------------------
Im alive: John
Im alive: Jack
Im alive: Jake
---------------------
John Jack Jake

Im no longer alive: John
Im no longer alive: Jack
Im no longer alive: Jake

所以在这个陈述中

a.push_back(Testi("John"));

由于表达式Testi("John")而创建了一个临时对象。 然后将此对象复制到向量,向量存储临时对象的副本。在语句结束时,临时对象将被删除。

Im alive: John
Im alive: John_copy
Im no longer alive: John

执行此声明时

a.push_back(Testi("Jack"));

执行相同的操作,但向量需要重新分配内存以容纳新元素。

Im alive: Jack
Im alive: John_copy_copy
Im no longer alive: John_copy
Im alive: Jack_copy
Im no longer alive: Jack

第一条消息对应于创建与参数Testi("Jack")对应的临时对象。然后,由于内存重新分配,向量的当前元素被复制到新的内存范围

Im alive: John_copy_copy
Im no longer alive: John_copy

然后复制新元素并删除临时对象

Im alive: Jack_copy
Im no longer alive: Jack

等等。

如果您在向量中保留足够的内存,则不会重新分配内存。此外,如果您使用emplace_back而不是push_back,则不会创建临时对象。在这种情况下,输出将是

Im alive: John
Im alive: Jack
Im alive: Jake

Im no longer alive: John
Im no longer alive: Jack
Im no longer alive: Jake

答案 2 :(得分:0)

您将临时对象作为参数传递给push_back。临时被复制,然后它的生命周期到期,因此被破坏了。

答案 3 :(得分:0)

如果您要重载副本并移动构造函数(并为方便起见跟踪实例),您将看到there

上发生了什么
struct Testi {
    static int instanceCount;

    std::string name;
    int instanceIndex;

    Testi(Testi&& other) : name(other.name), instanceIndex(++instanceCount) {
        std::cout <<other.instanceIndex << " => " << instanceIndex << " move constructor " << name << std::endl;
    }

    Testi(const Testi& other) : name(other.name), instanceIndex(++instanceCount) {
        std::cout <<other.instanceIndex << " => " << instanceIndex << " copy constructor " << name << std::endl;
    }
    Testi(std::string a) : name(a), instanceIndex(++instanceCount) {
        std::cout << instanceIndex << " Im alive: " << name << std::endl;
    }
    ~Testi() {
        std::cout << instanceIndex << " Im no longer alive: " << name << std::endl;
    }
};

int Testi::instanceCount = 0;
  

1我活着:约翰
  1 =&gt; 2移动构造函数约翰   1我不再活着:约翰
  3我活着:杰克
  3 =&gt; 4移动构造杰克
  2 =&gt; 5复制构造函数约翰   2我不再活着:约翰
  3我不再活着:杰克
  6我活着:杰克
  6 =&gt; 7移动建设者杰克
  5 =&gt; 8副本构造函数约翰   4 =&gt; 9副本构造函数杰克   5我不再活着:约翰
  4我不再活着:杰克   6我不再活着:杰克
  杰克

     

8我不再活着:约翰
  9我不再活着:杰克   7我不再活着:杰克

1 - 作为参数传递给push_back()的临时对象
2 - 存储在通过移动(或复制)创建的向量中的对象
3 - 另一个临时对象
4 - 存储在向量中的对象
5 - 对象2的副本,因为向量需要重新分配
等等。但这将为您提供实施的一般概念(以及为什么人们可能想要reserve())。标准不限制应该使用的确切内容。在这里我们还应该讨论版本:

  • 在C ++ 11之前包含的类型应该是可复制构造和可复制的
  • 从C ++ 11开始,限制适用于特定功能; push_back()包含的类型应为CopyInsertableMoveInsertable

这应该给你一般的实施期望。