【问题标题】:set.clear() calls destructors of contained elements before removing the elementset.clear() 在删除元素之前调用包含元素的析构函数
【发布时间】:2013-12-24 05:12:18
【问题描述】:

在下面的代码中,我希望断言通过,但它没有。

这与documented behavior of unique_ptr::reset 不同,我觉得这很令人惊讶。

我做错了什么还是一个错误?这是一个问题,因为如果相同的元素被删除一次增益,析构函数会被调用两次。

#include <set>
#include <memory>

struct F
    : std::enable_shared_from_this<F>
{
    static int destructor_count;
    static std::set<std::shared_ptr<F>> container;

    F() {}

    ~F() {
        assert(container.size() == 0);
        container.clear(); // This will delete the same pointer twice.
        destructor_count--;
    }
};

int F::destructor_count = 0;
std::set<std::shared_ptr<F>> F::container;

int main()
{
    F::container.insert(std::shared_ptr<F>(new F));
    F::container.clear();
    return 0;
}

编译器信息:

libstdc++6-4.6-dev

g++ (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
Copyright (C) 2011 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

【问题讨论】:

  • 旁注:使用std::make_shared
  • @chris 省去冗长的篇幅?
  • 就是这样,但有时也有异常安全性。例如,如果f 占用其中两个,f(new A(), new B()) 可能会导致泄漏。
  • 嗯...有趣。我没有意识到这一点。

标签: c++ stl g++ libstdc++


【解决方案1】:

unique_ptr 被明确记录为这种行为是有原因的:它比标准库的其余部分通常得到的保证更强。如果相同的规则适用于所有类型,则不需要专门为 unique_ptr 声明。

您的代码递归调用F::container.clear(),而另一个对F::container.clear() 的调用正在运行。这不能保证有效:

17.6.5.8 重入 [reentrancy]
除非在本标准中明确规定,否则标准 C++ 库中的哪些函数可以递归重新输入是由实现定义的。

现在不幸的是,libstdc++ 无法记录哪些函数可以递归重新输入,因此假设没有函数可以更安全,因此您的断言不正确。

【讨论】:

    【解决方案2】:

    清空容器时调用F的析构函数,此时容器不为空,因此断言失败。

    【讨论】:

    • 是的,但是如果我从析构函数中迭代容器,则会返回一个指向部分被破坏的对象的指针。这与 unique_ptr 的行为不同。
    • 否,但在 unique_ptr 上调用 reset() 会在销毁对象之前将值设置为 false。
    【解决方案3】:

    我不知道您为什么会认为该断言是正确的。您不应该知道容器如何保存和销毁内存的详细信息。你不应该对 std::set::clear() 期间发生的事情做出假设,只相信在它完成后将调用所有析构函数并且 std::set::size() 将返回 0。

    在 Dobbs 博士的这篇旧文章 STL's Red-Black Trees 中,描述了 std::set 背后的支持数据结构。在整个树为空之前,节点将被删除(并且它们的内容被破坏),但请放心,在 std::set::clear() 结束时,所有析构函数都将被调用,std::set::size() 将返回 0。

    注意:std::set 的其他实现可能使用不同的支持数据结构,红黑树只是一种可能的实现。

    【讨论】:

    • 我看不到元素的存储方式会影响何时调用析构函数。在销毁它们之前删除对包含元素的引用是很直接的。
    • 我的意思是,从树中删除对节点的引用然后删除节点应该是正确的,而不是相反。
    • @nishantjr 该实现符合标准,并以符合该规范的方式执行。如果您有超出标准的其他要求,则需要找到符合这些附加要求的实现或构建自己的实现。
    • 在销毁节点之前删除对节点的引用会花费更多代码并减慢其他所有人的程序,以支持您奇怪的用例。有可能支持这一点,但如果实现更愿意让clear() 更快地用于更常见的用例,则可以选择不支持(请参阅我的回答)。
    猜你喜欢
    • 2018-10-29
    • 2011-03-09
    • 1970-01-01
    • 2014-05-27
    • 2017-02-15
    • 1970-01-01
    • 2016-02-12
    • 2020-12-08
    • 1970-01-01
    相关资源
    最近更新 更多