【问题标题】:Why are my destructors being called a greater than number of times than expected?为什么我的析构函数被调用的次数比预期的要多?
【发布时间】:2012-12-09 16:47:16
【问题描述】:

我编写这段代码是为了检查 c++ 中析构函数的行为

#include <vector>
#include <iostream>

using namespace std;


class WrongDestructor
{
private:
    int number;
public:
    WrongDestructor(int number_) :
    number(number_)
    {}

    ~WrongDestructor() {

    cout<<"Destructor of " <<number<<endl;

//  throw int();
    }
};
int main(int argc, char *argv[])
{
    std::vector<WrongDestructor> wrongs;

    for(int i = 0; i < 10; ++i) {
    wrongs.push_back(WrongDestructor(i));
    }

    return 0;
}

我发现有趣的是我的程序的输出:

Destructor of 0
Destructor of 0
Destructor of 1
Destructor of 0
Destructor of 1
Destructor of 2
Destructor of 3
Destructor of 0
Destructor of 1
Destructor of 2
Destructor of 3
Destructor of 4
Destructor of 5
Destructor of 6
Destructor of 7
Destructor of 0
Destructor of 1
Destructor of 2
Destructor of 3
Destructor of 4
Destructor of 5
Destructor of 6
Destructor of 7
Destructor of 8
Destructor of 9
Destructor of 0
Destructor of 1
Destructor of 2
Destructor of 3
Destructor of 4
Destructor of 5
Destructor of 6
Destructor of 7
Destructor of 8
Destructor of 9

这意味着创建的对象比我想象的要多得多。当我在 for 循环中填充集合时,我希望集合中显然有 10 个,并且可能会创建下 10 个作为临时对象。但它们的数量更多,其中一些甚至比其他更频繁地创建。

【问题讨论】:

  • 能否将标题重命名为问题?

标签: c++ destructor


【解决方案1】:

vector 必须分配一个更大的内存块来保存元素时,新元素被移动构造到新的更大的内存块中。因为您的类型没有定义移动构造函数或复制构造函数,所以您将获得编译器提供的默认复制构造函数。默认的复制构造函数对类中的所有成员进行简单的成员复制。

此外,使用push_back 插入元素本身需要将其移动或复制到vector。因此,假设您的编译器没有对此进行优化,您也将在那里获得副本。 (请注意,您可以使用emplace_back 避免这些副本。)

因此,您会提前将多个实例副本插入容器中,因为当例如1 被复制到 vector 内部的一个更大的内存缓冲区中,它在旧的、更小的缓冲区中被销毁。

您可以通过定义复制和/或移动构造函数更清楚地看到这种行为:

#include <vector>
#include <iostream>

using namespace std;


class WrongDestructor
{
private:
    int number;
public:
    WrongDestructor(int number_) :
    number(number_)
    {}

    // Copy constructor
    WrongDestructor(WrongDestructor const& copied)
        : number(copied.number)
    {
        cout << "Copied " << this->number << endl;
    }

    ~WrongDestructor() {

    cout<<"Destructor of " <<number<<endl;

//  throw int();
    }
};
int main(int argc, char *argv[])
{
    std::vector<WrongDestructor> wrongs;

    for(int i = 0; i < 10; ++i) {
    wrongs.push_back(WrongDestructor(i));
    }

    return 0;
}

这个程序给出以下输出:http://ideone.com/S5Zf41

【讨论】:

    【解决方案2】:

    您的向量没有预先确定的大小。当您将对象推回向量时,它必须重新分配向量本身以容纳新条目。这意味着将对象从内存缓冲区复制到新缓冲区。因此,您会看到更多副本。

    【讨论】:

      【解决方案3】:

      您可以使用std::vector::reserve 为向量分配足够的空间。否则,vector 需要为传入的元素重新分配连续空间,从而导致大量副本。

      std::vector<WrongDestructor> wrongs;
      wrongs.reserve(10);
      

      【讨论】:

      • 请注意,OP 仍然会看到比预期数量更多的析构函数调用,因为元素是复制构造到向量中的。
      • 是的,我在回答中加了一点盐,因为你们的回答已经很好了。
      【解决方案4】:

      是的,看起来你在向量中添加了 10 个对象,但实际上又创建了 10 个对象,所以最后会销毁另外 10 个对象。

      你应该知道,当vector的内容越来越多时,vector会为更多的对象创造容量,在这个过程中发生了什么?

      如果向量没有空间容纳新对象,它会为更多对象创建更大的内存块,并复制它拥有的对象。然后添加新的(通过 push_back),然后它会destroy对象并释放满足它们的原始内存。

      所以,当复制更多对象创建时,当销毁原始对象时,会调用更多的析构函数。

      最好的方法是你提供复制构造函数,并在其中打印一些东西。 并且还会在ctor中打印一些东西,你会看到整个过程。

      更多你需要知道的是vector的容量和储备。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-01-16
        • 2018-02-03
        • 2013-01-12
        • 2010-12-21
        • 1970-01-01
        • 1970-01-01
        • 2011-02-07
        • 2021-11-06
        相关资源
        最近更新 更多