C++11清除shared_ptr时,我应该使用reset还是设置为nullptr?

97

我对C++11最佳实践有一个问题。当清除一个共享指针时,我应该使用没有参数的reset()函数,还是将shared_ptr设置为nullptr?例如:

std::shared_ptr<std::string> foo(new std::string("foo"));
foo.reset();
foo = nullptr;

这两种方法有真正的区别吗?还是每种方法都有优缺点?


4
foo = {} 也是一种选择! - Luc Danton
1
这个怎么样 -> std::shared_ptr<std::string> bar; foo = bar?通常情况下,当我们将一个 shared_ptr 赋值给另一个 shared_ptr 时,它所指向的对象的引用计数会增加。那么在这种情况下会发生什么呢? - rivaldo4t
如果foo在赋值点引用另一个对象,那么它的共享引用计数会自动减少——在赋值之前——就像显式调用reset或在赋值之前分配nullptr一样。如果该引用计数现在为零,则之前所引用的对象将被销毁。然后,与bar共享的引用计数会按正常方式递增。 - Peter Forrest
4个回答

104
这两种方法是完全等价的,也就是说第二种形式(foo = nullptr)是基于第一种形式定义的。根据C++11标准的第20.7.1.2.3/8-10段:
 unique_ptr& operator=(nullptr_t) noexcept;

因此,只需选择最能清晰表达意图的方法。就我个人而言,我更喜欢:

foo = nullptr;

由于这使得我们更清楚地表明指针应该是空的。然而,作为一般建议,请尽量减少需要显式地重置智能指针的情况。


此外,不要使用new

std::shared_ptr<std::string> foo(new std::string("foo"));

尽可能使用std::make_shared()

auto foo = std::make_shared<std::string>("foo");

1
面向对象编程,谢谢指出错误。我在编写字符串时没有注意到。已更新问题以反映更改。 - user1930581
1
我个人会像 @Andy Prowl 所说的那样使用 nullptr。但是尽量编写您的代码,以便 shared_ptr 超出范围。 - Trax
2
@MarkB:异常安全和少一次分配内存:更详细的讨论,请参见这个答案 - Andy Prowl
7
好的,三年已经过去了。希望有人会读到这篇文章...我注意到你引用的标准是针对unique_ptr的,并且shared_ptr没有operator=(nullptr_t)方法。如果我使用foo = nullptr,那么nullptr会被转换为unique_ptr,然后再转换为shared_ptr,这样理解是否正确? - mdr
5
我感到困惑的是一个回答中谈论了“另一个类”,但从未提及它却得到了高投票。正如@mdr所说,以及r0ng所展示的(虽然没有足够的细节或关注优化),shared_ptr没有operator=(nullptr_t),因此将nullptr分配给它需要进行转换。我们可能可以证明在优化构建中这无关紧要,但说“两个替代方案完全等价,即第二种形式(foo = nullptr)是基于第一种形式定义的”似乎完全是错误的。 - underscore_d
显示剩余2条评论

17

我更倾向于使用 reset(),因为它能明确表达意图。但是,尽可能编写您的代码,使您不需要显式清除 shared_ptr<>,即确保在您本应清除它时,shared_ptr<> 退出其作用域。


你能告诉我为什么不建议清除指针的原因吗?是因为会有性能损失还是只是没有空指针存在的问题? - Robert F.
3
不会有性能损失。只是,需要频繁清除指针可能暗示着设计上的缺陷。 - Walter

5

如果您使用https://godbolt.org/进行检查,它们会有一些不同
通过使用gcc(7.2)
foo.reset(); 生成的汇编代码

  lea rax, [rbp-32]
  mov rdi, rax
  call std::__shared_ptr<int, (__gnu_cxx::_Lock_policy)2>::reset()

然而,foo = nullptr; 生成的结果是

  lea rax, [rbp-16]
  mov esi, 0
  mov rdi, rax
  call std::shared_ptr<int>::shared_ptr(decltype(nullptr))
  lea rdx, [rbp-16]
  lea rax, [rbp-32]
  mov rsi, rdx
  mov rdi, rax
  call std::shared_ptr<int>::operator=(std::shared_ptr<int>&&)
  lea rax, [rbp-16]
  mov rdi, rax
  call std::shared_ptr<int>::~shared_ptr()

它使用 nullptr 创建一个共享指针,将新创建的对象分配给变量,并调用析构函数来销毁字符串。

由于我不知道如何检查 reset() 函数中发生了什么。无法确定哪个更快。


19
如果你在-O2下将它们都编译,你会发现在发布版本中没有任何区别。 - RandomGuy

-1

通常情况下,智能指针可以自行处理。但如果您需要解决方案,reset() 在我看来是最好的选择。


8
仅仅陈述一个观点并不能回答这个问题,因为问题关键在于请求给出推理:「两种方法是否存在实质差异或者有其各自的优劣之处?」 - underscore_d

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接