unique_ptr 的有效性

cpp*_*der 11 c++ x86-64 compiler-optimization unique-ptr

我喜欢std::unique_ptr在 c++11 中看到它的那一刻,但我质疑它的有效性很长一段时间。(有关实时代码的链接,请参见下面的链接):

#include <memory>

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

void foo()
{
    val = get();
}
Run Code Online (Sandbox Code Playgroud)

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

比我尝试这样做:

void foo()
{
    auto ptr = get().release();
    val.reset(ptr);
}
Run Code Online (Sandbox Code Playgroud)

突然间只有 11 条指令。然后我更深入地破解了 unique_ptr move ctor。最初它是作为reset(__u.release());. 我基本上只是按如下方式重新排序:

auto& ptr =  _M_ptr();
if (ptr)
    _M_deleter()(ptr);
ptr = __u.release();
Run Code Online (Sandbox Code Playgroud)

Aaand.... 手动管理版本中的 11 条指令。它略有不同,但似乎还可以。

在这里保存了我的实验。

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

Jef*_*ett 4

移动分配的操作顺序必须是:

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

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

逻辑必须采用这种方式的原因是源可能由目的地拥有。

想象:

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

head->next如果在删除 管理的旧对象之前未将 的托管指针清空,则此操作将无法正确运行head

因此,在代码中,移动分配就是:

reset(source.release())
Run Code Online (Sandbox Code Playgroud)

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

auto& ptr =  _M_ptr();
if (ptr)
    _M_deleter()(ptr);
ptr = __u.release();
Run Code Online (Sandbox Code Playgroud)

这就留下了你的问题:为什么

val = get();
Run Code Online (Sandbox Code Playgroud)

不同于

auto ptr = get().release();
val.reset(ptr);
Run Code Online (Sandbox Code Playgroud)

区别在于unique_ptr<int>从 隐式返回get()release在第一个版本中,它在和后被销毁resetrelease在第二个版本中,它在 之后但之前被销毁reset。在这两种情况下,它在被销毁时都为空,并且不需要删除。但是编译器一定无法在reset.