【问题标题】:std::vector doesn't call destructor on reassignmentstd::vector 在重新分配时不调用析构函数
【发布时间】:2018-10-25 16:55:36
【问题描述】:

我正在尝试使用std::vector 来保存一些S 实例。但是,当我重新分配向量的成员时,不会在前一个租户上调用析构函数:

代码

#include <iostream>
#include <vector>

struct S {
    int index_;
    S(int index) : index_(index) {
        std::cout << "Calling S " << index_ << " constructor\n";
    }
    S(const S& other) : index_(other.index_) {
        std::cout << "Calling S " << index_ << " copy constructor\n";
    }
    ~S() {
        std::cout << "Calling S " << index_ << " destructor\n";
    }
};


int main()
{
    std::vector<S> v;
    v.reserve(10); // Let's not worry about copy constructors
    std::cout << "# Created vector\n";
    v.emplace_back(0);
    v.emplace_back(1);
    v.emplace_back(2);
    std::cout << "# Replacing\n";
    v[1] = S(3); // This doesn't destruct S(1) that was here

    // I can manually call the destructor before I reassign but is that
    // something I should do()?
    // v[1].~S();

    std::cout << "# End scope\n";
}

输出

# Created vector
Calling S 0 constructor
Calling S 1 constructor
Calling S 2 constructor
# Replacing
Calling S 3 constructor
Calling S 3 destructor
# End scope
Calling S 2 destructor
Calling S 3 destructor
Calling S 0 destructor

问题

所以看起来S(1) 在位置1 永远不会被破坏。正如我在代码中指出的那样,我可以在重新分配析构函数之前手动调用它,但我不确定这是否是个好主意。是吗,如果不是,你有什么建议?还有,有没有

连接到我真正想要的东西

在实际代码中,我正在使用二叉树,我认为让节点成为向量的成员并通过向量中的索引相互指向会很有趣(让我获得连续内存缓存的好处,32 位索引而不是 64 位指针,以及一些不同的东西)。但最终,我需要对树进行一些操作,这意味着移动/删除元素,所以我希望调用析构函数来删除元素(我将使用std::set 或其他东西来跟踪向量)。

【问题讨论】:

  • 你忘了检测operator=
  • 在赋值时不调用析构函数(如果你愿意,也可以“重新赋值”)..
  • @Miles Budnek 并移动构造/移动分配(为了完整性)..
  • @JesperJuhl 是的,但如果有用户提供的副本,则不会生成默认的移动构造函数/赋值运算符,因此缺少这些将不那么神秘。
  • @Miles Budnek 我知道这一点。我的观点只是为了完整性,如果 OP 稍后还想测试在某些情况下类型是可移动的情况下会发生什么(例如,如果调整对象向量的大小为 move_if_noexcept == true)。你知道,只是为了获得更完整的画面。

标签: c++ memory vector destructor


【解决方案1】:

分配给向量元素将调用复制赋值运算符,而不是复制构造函数。这就是你需要的东西

struct S {
    int index_;
    S(int index) : index_(index) {
        std::cout << "Calling S " << index_ << " constructor\n";
    }
    S(const S& other) : index_(other.index_) {
        std::cout << "Calling S " << index_ << " copy constructor\n";
    }

    // added
    S& operator=(const S& other) {
        if (this == &other) { return *this; }
        std::cout << "Calling S " << index_ << " copy assignment\n";
        index_ = other.index_;
        return *this;
    }

    ~S() {
        std::cout << "Calling S " << index_ << " destructor\n";
    }
};

分配不会破坏现有对象,它会分配给它。你可以用一个更简单的例子来重现这个

int main() {
    S s1(1);
    s1 = S(2); // assignment, not destruction/construction. same idea
}

如果您的对象拥有一些资源,您会发现赋值运算符与析构函数和复制构造函数的作用相似。您可以 read about the rule of 3 here 扩展为 5 的规则,并添加移动操作。

【讨论】:

  • 投反对票的人应该明确说明他们投反对票的原因。
  • 你提到了 3 和 5 的规则,为了完整性,可能会添加 0 的规则。
  • @JesperJuhl。这与答案并不相关,但可以肯定
  • 谢谢。最后一个例子确实做到了。
猜你喜欢
  • 2017-06-11
  • 1970-01-01
  • 2020-08-14
  • 2020-04-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多