5

在下面的代码中,一个 X 注册在一个全局容器中,该容器成为它的共享所有者。X 的析构函数测试它不再是此类所有权的一部分,我希望这是被销毁的有效先决条件。

#include <vector>
#include <memory>

struct X {
    ~X();
};

std::vector<std::shared_ptr<X> > global_x_reg;

X::~X()
{
    for (auto iter = global_x_reg.begin(), end = global_x_reg.end(); iter != end; ++iter)
        if (iter->get() == this)
            throw "Oops. X gets destroyed while it is still owned!";
}

int main(int argc, char** argv)
{
    global_x_reg.push_back( std::shared_ptr<X>(new X) );
    global_x_reg.clear(); // calls X::~X().
}

当它运行时(使用 VS2010 编译后),当容器被清除时会抛出“Oops...”。

问题:

  1. 这个代码合法吗?如果不是,为什么不呢?如果是这样,它应该扔吗?
  2. std 容器是否应该clear()以这样的方式实现,即在销毁其值期间,这些值不再作为容器可见。
  3. 应该std::shared_ptr::get,什么时候std::shared_ptr销毁它的指针,返回nullptr
4

3 回答 3

4

根据 N3936 [basic.life]/1:“具有非平凡初始化的对象的生命周期在析构函数调用开始时结束。”和 /3:

本国际标准中赋予对象的属性仅在其生命周期内适用于给定对象。[注意:特别是,在对象的生命周期开始之前和其生命周期结束之后,对对象的使用有很大的限制,如下所述,在 12.6.2 和 12.7 中。此外,正在构建和销毁的对象的行为可能与生命周期已经开始但尚未结束的对象的行为不同。12.6.2 和 12.7 描述了对象在构建和销毁阶段的行为。——<em>尾注]

您在 ashared_ptr生命周期结束后调用成员函数。由于无法知道给定的标准库类成员函数是否符合指定的限制,因此在标准库对象的生命周期结束后对其调用成员函数因此具有未定义的行为,除非另有说明。

另请参阅图书馆工作组问题 2382“容器更新与删除对象时的对象破坏的顺序不明确”,这与该问题非常相关。global_x_reg一般来说,在处理成员函数调用的过程中(global_x_reg.clear()在这种情况下)重新输入标准库对象 ( ) 不是一个好主意。类不变量显然必须在成员调用之前和之后保持,但不能保证对象在调用期间处于有效状态。

于 2014-06-26T14:12:31.973 回答
2

我认为在删除.get() NULL(因此,~X())被std::shared_ptr<X>::~shared_ptr<X>. 唯一的要求是它NULLstd:shared_ptr<X>::~shared_ptr<X>完成后返回。

类似地,如果std::vector使用placement new 来构造和销毁它的元素(它将会),没有理由在销毁放置的元素之前必须更新它的访问器。

实际上,如果您考虑一下这种常见的老式模式:

T* ptr = new T();
delete ptr;
ptr = NULL;

很明显,~T()将调用之前ptr设置为NULL; 这与您所看到的几乎相同。

以下来自 libstdc++ v4.6.3 的摘录支持此类比:

00353       virtual void
00354       _M_destroy() // nothrow
00355       {
00356     _My_alloc_type __a(_M_del);
00357     this->~_Sp_counted_deleter();
00358     __a.deallocate(this, 1);
00359       }

链接

简而言之,您正在观察一个完全无害的实现细节,并试图断言它破坏了声明的语义,而我认为它不会。

于 2014-06-26T14:06:35.033 回答
0

我认为你在这里做了一件奇怪的事情。如果您想继续访问您的元素,您应该X::~X()以这样一种方式实现,即销毁的对象未注册但不清除 std::vector 本身。此外,共享指针在相关调用中无效(并且 X 被破坏clear()std::vector。因此,std::vector它处于脆弱状态,无论如何我都不会过多依赖它。考虑一个链表的例子:如果你删除一个在进程中被销毁的项目并且析构函数遍历链表,可能是对节点的引用不处于一致状态的情况。我会避免这种情况。

于 2014-06-26T14:13:28.303 回答