30

好的,首先是一些可能相关的事情:

我在 C++11 模式下使用 Clang 3.1 编译器,标准库设置为 libc++。

我正在尝试让自己熟悉 C++11,在这样做的过程中,我遇到了看起来很奇怪的行为。这可能是 Clang 或 libc++ 的一个怪癖,但我不会说 C++ 标准语言,而且我无法访问其他支持 C++11 的编译器,所以我无法真正检查它,我已经搜索了互联网和 Stack Overflow尽我所能,没有找到任何相关的东西……所以我们开始吧:

当使用 shared_ptr / unique_ptr 为简单资源实现 RAII 时,它们的行为似乎在删除时针对空指针而有所不同。我意识到通常没有必要删除空指针,但我曾期望该行为至少在两个 STL 智能指针之间匹配。

对于特定情况,请考虑以下代码:

{
    auto Deleter = [](void *){cout << "It's later!" << endl;};
    shared_ptr<void> spDoSomethingLater(nullptr, Deleter);
    unique_ptr<void, void (*)(void *)> upDoSomethingLater(nullptr, Deleter);
    cout << "It's now!" << endl;
}

我本来期望从中得到以下输出之一:

a) 如果两个删除器都被调用,即使指针为空:

"It's now!"
"It's later!"
"It's later!"

b) 如果因为指针为空而没有调用删除器:

"It's now!"

但我没有观察到这两种情况。相反,我观察到:

"It's now!"
"It's later!"

这意味着正在调用一个而不是另一个删除器。经过进一步调查,我发现shared_ptr的删除器被调用,无论它是否持有空值,但unique_ptr的删除器只有在它不持有空值时才会被调用。

我的问题:这实际上是标准规定的正确行为吗?如果是这样,为什么两种 STL 类型之间的指定行为会以这种方式有所不同?如果不是,这是我应该向 libc++ 报告的错误吗?

4

2 回答 2

28

观察到的行为符合标准。

对于unique_ptr,20.7.1.2.2/2(析构函数)说

效果:如果get() == nullptr没有效果。否则 get_deleter()(get())

对于shared_ptr, 20.7.2.2.2/1 表示即使包装了空指针,也应该调用删除器:

效果:

  • 如果 *this 为空或与另一个 shared_ptr实例 ( use_count() > 1) 共享所有权,则没有副作用。
  • 否则,如果 *this 拥有一个对象p和一个删除器dd(p)则调用。
  • 否则, *this 拥有一个指针p,并被delete p调用。

这里重要的细节是表达“拥有一个对象p”。20.7.2.2/1 说“如果一个shared_ptr对象不拥有指针,它就是空的”。20.7.2.2.1/9(相关构造函数)说它“构造一个拥有对象和删除器的对象”。shared_ptrpd

据我所知,该调用在技术上使shared_ptr 自己成为空指针,这导致删除器被调用。将此与无参数构造函数进行对比,后者据说会留下shared_ptr”。

于 2012-06-22T21:37:28.033 回答
10

是的,这是正确的行为。

§20.7.1.2.2[unique.ptr.single.dtor]/2:

unique_ptr析构函数

效果:如果get() == nullptr没有效果。否则get_deleter()(get())

§20.7.2.2.2[util.smartptr.shared.dest]/1:

shared_ptr析构函数

效果

  • 如果*thisshared_ptr或与另一个实例共享所有权( use_count() > 1),则没有副作用。
  • 否则,如果*this 拥有一个对象p和一个删除器dd(p)则调用。
  • 否则,*this 拥有一个指针,并调用pdelete 。p

所以应该没有效果,因为 shared_ptr 是空的?错误,因为提供 a 指针会使 shared_ptr为空,即使指针为空。

§20.7.2.2.1[util.smartptr.shared.const]/8-10

shared_ptr构造函数

template<class Y, class D> shared_ptr(Y* p, D d);
template<class Y, class D, class A> shared_ptr(Y* p, D d, A a);
template <class D> shared_ptr(nullptr_t p, D d);
template <class D, class A> shared_ptr(nullptr_t p, D d, A a);

要求: p应可转换为T*. …</p>

效果:构造一个拥有该对象shared_ptr和删除器的对象。pd

后置条件: use_count() == 1 && get() == p .

这意味着 shared_ptr拥有nullptr。因此,当 shared_ptr 被销毁时,将调用删除器。

于 2012-06-22T21:41:14.697 回答