【问题标题】:Destructor gets called when I don't want it to当我不想调用析构函数时
【发布时间】:2014-12-11 19:40:42
【问题描述】:

我创建的类的析构函数在作用域结束之前被调用。我认为当我向它添加另一个元素时,它与向量中的重新分配有关。我如何超越这个问题?我希望仅当对象到达我的代码中的范围末尾时才调用析构函数。

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

using namespace std;

class A
{
public:
    ~A() { cout << "destructor called\n"; }
};

int main ()
{
    A one, two;

    vector<A> vec;
    cout << "push_back one" << endl;
    vec.push_back(one);
    cout << "push_back two" << endl;
    vec.push_back(two);
    //destructor gets called here
    system("pause");
    return 0;
} //while i want it to be called here

【问题讨论】:

  • 在向量上使用保留以防止重新分配。
  • 您必须致电reserve()
  • 我正在寻找不同的解决方法。调用 reserve() 对我来说听起来像是一个骗局。我想完全理解这个问题。也许通过添加一些代码让 A 类“更智能”?
  • 与您的问题无关,但using namespace std;system("pause"); 是我最讨厌的两个问题。
  • 如果向量的缓冲区空间不足,需要分配新的缓冲区,则必须销毁旧缓冲区的内容。没有办法解决这个问题。

标签: c++ class oop destructor


【解决方案1】:

您的代码中的打印输出并未向您显示完整的图片:为不同的对象调用析构函数。

看看这个修改后的代码。我添加了地址的打印输出,并登录了其他一些关键位置,例如赋值运算符和复制构造函数的调用。

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

using namespace std;

class A
{
public:
    A() {cout << "constructed " << (void*)this << "\n";}
    A(const A& a) {cout << "copied " << (void*)(&a) << " to " << (void*)this << "\n"; }
    A& operator =(const A& a) {cout << "assigned " << (void*)(&a) << " to " << (void*)this << "\n"; }
    ~A() { cout << "destructor called for " << (void*)this << "\n"; }
};

int main ()
{
    A one, two;

    vector<A> vec;
    cout << "push_back one" << endl;
    vec.push_back(one);
    cout << "push_back two" << endl;
    vec.push_back(two);
    //destructor gets called here
    return 0;
}

它产生以下输出 (demo):

constructed 0xbff229b2
constructed 0xbff229b3
push_back one
copied 0xbff229b2 to 0x970c008
push_back two
copied 0xbff229b3 to 0x970c019
copied 0x970c008 to 0x970c018
destructor called for 0x970c008
destructor called for 0x970c018
destructor called for 0x970c019
destructor called for 0xbff229b3
destructor called for 0xbff229b2

您可以看到对象onetwo0xbff229b20xbff229b3)直到最后才被销毁,当它们超出范围时。调整矢量大小时,它们的副本会被销毁。

【讨论】:

  • 有没有办法知道什么时候只有原始对象被销毁?
  • @Pilpel 不完全是——在调用析构函数时,C++ 在堆栈上创建的对象和复制的对象之间没有区别。接近这一点的一种方法是在对象中存储一个标志,该标志会向析构函数发出它是“原始”对象的信号,并且不在复制构造函数或赋值运算符中复制该标志。
【解决方案2】:

根据 C++ 标准第 23.3.6 节:

向量具有特定的容量,含义

向量可以容纳而不需要重新分配的元素总数

当您push_back 一个附加元素时,这实际上将容器大小增加了一倍,这

如果新大小大于旧容量,则导致重新分配

重新-分配意味着您正在de-分配(即销毁)向量中的元素,然后再次分配(即构造)它们。

请注意,在您的情况下,A 的原始 onetwo 实例不会被销毁,而是它们存储在向量中的副本被销毁。

程序员需要更加小心引用和迭代器,因为

重新分配使所有引用序列中元素的引用、指针和迭代器无效。

正如其他人所提到的,reserve() 将有助于防止这种情况发生,因为

在调用 reserve() 后发生的插入过程中不会发生重新分配 直到插入会使向量的大小大于 capacity() 的值。

【讨论】:

    【解决方案3】:

    按照 cmets 的建议,您应该使用 std::vector&lt;&gt;::reserve(size_t)

    您看到的是实现定义的。大多数实现在增长时大约是保留分配大小的两倍。这是为了避免在大小增加时进行多次分配和复制。

    Reserving 只是向集合建议您将需要 n 个元素。如果实现选择尊重请求,则重新分配并将现有值移动/复制到新分配中,该分配足够大以容纳您请求的元素数量。现在您可以在不进行昂贵的重新分配的情况下进行回退。当容器调整大小时,如果您清楚最终大小将是多少,正确保留可以为您节省许多重新分配。

    在这种情况下保留将避免调整大小以及破坏您看到的临时对象。

    您可能会看到析构函数,因为集合正在调整大小。当您第二次向后推时,它会重新分配。

    如果对象永远不能被销毁,你应该考虑另一种集合类型或存储位置,因为程序以其当前形式依赖于实现定义的行为(即标准的向量规范不做出您需要的保证)。

    【讨论】:

      猜你喜欢
      • 2014-03-14
      • 1970-01-01
      • 2019-11-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-23
      • 2015-03-29
      • 2012-09-02
      相关资源
      最近更新 更多