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。
有人可以指出是我遗漏了什么还是实际上是有意为之?
移动赋值的操作顺序必须是:
- 复制托管源指针,并在源对象中将其置空。
- 如果目标托管指针不为空,则将其删除。
- 在目标对象中设置目标托管指针。
请注意,此逻辑很高兴地导致自移动分配的空操作。
逻辑必须是这样的原因是源可能属于目标。
想象一下:
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>
。在第一个版本中,它在 release
和 reset
之后被销毁。在第二个版本中,它在 release
之后但在 reset
之前被销毁。在这两种情况下,它在销毁时都已清空,并且不需要删除。但是编译器必须无法传播此指针在 reset
.
中为空的知识。
当我在 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。
有人可以指出是我遗漏了什么还是实际上是有意为之?
移动赋值的操作顺序必须是:
- 复制托管源指针,并在源对象中将其置空。
- 如果目标托管指针不为空,则将其删除。
- 在目标对象中设置目标托管指针。
请注意,此逻辑很高兴地导致自移动分配的空操作。
逻辑必须是这样的原因是源可能属于目标。
想象一下:
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>
。在第一个版本中,它在 release
和 reset
之后被销毁。在第二个版本中,它在 release
之后但在 reset
之前被销毁。在这两种情况下,它在销毁时都已清空,并且不需要删除。但是编译器必须无法传播此指针在 reset
.