unique_ptr 的有效性

Effectiveness of unique_ptr

当我在 c++11 中看到它时,我很喜欢 std::unique_ptr,但我质疑它的有效性有一段时间了。 (请参阅下面的 link 以获得 link 实时代码):

#include <memory>

std::unique_ptr<int> get();
extern std::unique_ptr<int> val;

void foo()
{
    val = get();
}

这给了我关于 -O3 最后 clang 的 16 条指令。但更有趣的是,它生成了对 delete 的两次调用,即使第二次调用永远不会被调用。

比起我尝试这样做:

void foo()
{
    auto ptr = get().release();
    val.reset(ptr);
}

突然之间只有 11 条指令。然后我更深入地攻击了 unique_ptr move ctor。最初它被实现为 reset(__u.release());。我基本上只是重新排序如下:

auto& ptr =  _M_ptr();
if (ptr)
    _M_deleter()(ptr);
ptr = __u.release();

Aaand.... 11条指令与手控版相同。略有不同,但看起来还不错。

我保存了我的实验 here

有人可以指出是我遗漏了什么还是实际上是有意为之?

移动赋值的操作顺序必须是:

  1. 复制托管源指针,并在源对象中将其置空。
  2. 如果目标托管指针不为空,则将其删除。
  3. 在目标对象中设置目标托管指针。

请注意,此逻辑很高兴地导致自移动分配的空操作。

逻辑必须是这样的原因是源可能属于目标。

想象一下:

struct list { std::unique_ptr<list> next; };
std::unique_ptr<list> head;
// ...
if (head) head = std::move(head->next);

如果在删除由 head 管理的旧对象之前 head->next 的托管指针未被清空,这将无法正常运行。

在代码中,移动赋值因此只是:

reset(source.release())

您的最后一个片段显然无法在删除前将源清空,因此不是移动分配的可行实现:

auto& ptr =  _M_ptr();
if (ptr)
    _M_deleter()(ptr);
ptr = __u.release();

这就留下了关于为什么的问题

val = get();

不同于

auto ptr = get().release();
val.reset(ptr);

区别在于从 get() 返回的隐式 unique_ptr<int>。在第一个版本中,它在 releasereset 之后被销毁。在第二个版本中,它在 release 之后但在 reset 之前被销毁。在这两种情况下,它在销毁时都已清空,并且不需要删除。但是编译器必须无法传播此指针在 reset.

中为空的知识。