假设我有一个函数消耗
std::unique_ptr
:
void foo(std::unique_ptr<int>);
我这样称呼:
foo(std::make_unique<int>(0));
与此功能比较:
void foo(int*);
我这样称呼:
foo(new int{0});
我希望代码生成器几乎完全相同(尽管我认为可能存在关于传递或不传递寄存器的烦人的 ABI 问题)。尽管如此,我仍在想象
std::make_unique
调用 new int{0}
,移动构造 foo
的参数,所有这些我认为都会得到优化,包括 nullptr
签入 ~unique_ptr()
。
然而,幸福的道路是:
test():
push rbx
sub rsp, 16
mov edi, 4
call operator new(unsigned long)@PLT
mov dword ptr [rax], 0
mov qword ptr [rsp + 8], rax
lea rdi, [rsp + 8]
call foo(std::unique_ptr<int, std::default_delete<int>>)@PLT
mov rdi, qword ptr [rsp + 8]
test rdi, rdi
je .LBB0_3
mov esi, 4
call operator delete(void*, unsigned long)@PLT
.LBB0_3:
add rsp, 16
pop rbx
ret
与
new
版本相比,优化为:
test():
push rax
mov edi, 4
call operator new(unsigned long)@PLT
mov dword ptr [rax], 0
mov rdi, rax
pop rax
jmp foo(int*)@PLT
https://godbolt.org/z/PbsxjTdKW
如果我使用
std::unique_ptr<int, Nop>
(使删除器消失),我会得到更像我期望的东西:
https://godbolt.org/z/r4PcGn78E
为什么优化器不能看透参数的传输并优化掉对
delete
的任何调用?
一切都优化后,我想象我们:
致电
new int(0)
(在std::make_unique
内)。这可能会抛出异常,但如果确实如此,那么就没有 int
到 delete
。
可能将参数移动构造为
foo
(除非已经从std::make_unique
进行了RVO)。如果发生的话,那就noexcept
。
调用
foo
——但foo
不会在结束时破坏它自己的参数}
?
可能会破坏临时文件(如果有)——但它显然处于移出 (
nullptr
) 状态。
FWIW,使
foo()
成为 noexcept
会稍微减少代码生成 - 但仍然存在潜在的 delete
。
使用
make_unique
的调用正在创建一个临时对象。 当该临时对象超出范围时,将调用其析构函数。 您知道,当复制参数时,该临时变量将处于指针为空的状态,并且析构函数将无事可做 - 但编译器不知道这一点。 因此,无论如何它都必须调用析构函数,并且该调用将成为代码的一部分。