【问题标题】:unique_ptr: what is the effect of calling reset before assignmentunique_ptr:赋值前调用reset有什么效果
【发布时间】:2020-05-30 03:31:41
【问题描述】:

在 c++14 中,分配给现有的 unique_ptr 有什么区别:

    std::unique_ptr<double> p = std::make_unique<double>(1.0);
    p = std::make_unique<double>(2.0);

在分配给它之前首先调用reset:?

    std::unique_ptr<double> p = std::make_unique<double>(1.0);
    p.reset();
    p = std::make_unique<double>(2.0);

我认为在赋值之前添加.reset() 不会对代码产生很大影响,但是没有reset() 的那个在使用-O3 编译时似乎会产生不同(甚至更多)的汇编代码。在线查看代码:

https://godbolt.org/z/mBApWH

https://godbolt.org/z/JfStmC

【问题讨论】:

  • 您所说的“等效”是什么意思?在第二个版本中,您将 ptr 重置两次,在第一个版本中,只需一次,因此产生不同的汇编代码是正常的。
  • 你为什么要做第二个?充其量它的性能相当,但也可能更糟,更不用说整体可读性更差了。
  • 一页二编译器两个版本godbolt.

标签: c++ c++14 reset unique-ptr


【解决方案1】:

似乎有机会改进 GCC 优化器。

在第二个版本中我们得到

        call    operator new(unsigned long)
        mov     QWORD PTR [r12], 0                     ; [r12] is 0
        mov     esi, 4
        mov     rdi, rax
        call    operator delete(void*, unsigned long)
        mov     edi, 4
        call    operator new(unsigned long)
        mov     rdi, QWORD PTR [r12]                   ; rdi=[r12] == 0 (duh)
        mov     DWORD PTR [rax], 1111
        mov     QWORD PTR [r12], rax
        test    rdi, rdi                               ; unnecesary test
        je      .L1                                    ; branch always taken
        mov     esi, 4                                 ; unreachable code
        call    operator delete(void*, unsigned long)  ;
.L1:

clang 9另一方面,为第二个版本生成much smaller code,因为它能够消除第一个new/delete

        push    rbx
        mov     rbx, rdi
        mov     qword ptr [rdi], 0
        mov     edi, 4
        call    operator new(unsigned long)
        mov     dword ptr [rax], 1111
        mov     qword ptr [rbx], rax
        mov     rax, rbx
        pop     rbx
        ret

【讨论】:

  • 他的问题是为什么编译器对前者的优化不如后者。
【解决方案2】:

我相信编译器输出之间的大部分差异是由于两个 sn-ps 之一能够优化更多内容。毕竟,您只需将几个常量添加到一个变量中。如果我们要让优化器的工作更加困难,输出是nearly identical。那里的编译器不知道foo 是如何工作的,必须同时产生对它的调用并将结果添加到x,没有聪明的余地。

回答你的问题,差别很小。 unique_ptr 的移动赋值运算符无论如何都会释放内存,因此在它之前调用 reset 是多余的。但是,从我的链接中可以明显看出,如果编译器知道指针被重置,它可能会忽略该操作。

但是,如果指针持有的内存将在分配新内存之前被释放,那么之前调用 reset 可能会很有用,从而减少程序的内存占用。

【讨论】:

  • 很好的例子,谢谢。我认为首先调用 reset 并不完全是多余的,因为它确实改变了操作的顺序:第一个 double 的内存在分配第二个内存之前被释放。
猜你喜欢
  • 2012-12-01
  • 2023-04-03
  • 1970-01-01
  • 2017-03-03
  • 2013-07-19
  • 2018-07-29
  • 1970-01-01
  • 2014-05-12
  • 2018-10-13
相关资源
最近更新 更多