我应该使用shared_ptr还是unique_ptr

Cli*_*ton 52 c++ pimpl-idiom shared-ptr unique-ptr c++11

我一直在使用pimpl成语制作一些对象,但我不确定是否使用std::shared_ptrstd::unique_ptr.

据我所知,std::unique_ptr是更有效的,但是这是没有这么多的问题,对我来说,因为这些物体是比较重量级反正做的成本std::shared_ptrstd::unique_ptr相对较小.

我目前std::shared_ptr只是因为额外的灵活性.例如,使用a std::shared_ptr允许我将这些对象存储在散列映射中以便快速访问,同时仍然能够将这些对象的副本返回给调用者(因为我相信任何迭代器或引用可能很快变得无效).

但是,这些对象确实没有被复制,因为更改会影响所有副本,所以我想知道也许使用std::shared_ptr和允许副本是某种反模式或坏事.

它是否正确?

Mat*_* M. 37

我一直在使用pimpl成语制作一些对象,但我不确定是否使用shared_ptrunique_ptr.

绝对unique_ptrscoped_ptr.

Pimpl不是一种模式,而是一种成语,它处理编译时依赖性和二进制兼容性.它不应该影响对象的语义,特别是关于它的复制行为.

你可以使用你想要的任何类型的智能指针,但那些2保证你不会意外地在两个不同的对象之间共享实现,因为它们需要有意识地决定复制构造函数和赋值运算符的实现.

但是,这些对象确实没有被复制,因为更改会影响所有副本,所以我想知道也许使用shared_ptr和允许副本是某种反模式或坏事.

它不是反模式,事实上,它是一种模式:别名.您已经在C++中使用它与裸指针和引用.shared_ptr提供额外的"安全"措施,以避免死引用,代价是额外的复杂性和新问题(谨防造成内存泄漏的周期).


与Pimpl无关

我的理解unique_ptr是更有效的,但是这是没有这么多的问题,对我来说,因为这些物体是比较重量级反正做的成本shared_ptrunique_ptr相对较小.

如果您可以分解某些状态,您可能需要查看Flyweight模式.

  • @Clinton:从语义上讲,副本与其源代码分开,实现细节对用户不感兴趣:)在C++中,这不是指针和引用的情况,但是词汇表从未真正适应过,因此尴尬时讨论这些方面...在您的情况下,您更喜欢深拷贝还是浅拷贝应该只取决于您希望提供给您的类的语义,并且实现将遵循套件. (3认同)

Jam*_*nze 11

如果你使用shared_ptr,它不是真正的经典pimpl习语(除非你采取额外的步骤).但真正的问题是你为什么要使用智能指针开始; delete应该发生的地方非常清楚,并且不存在例外安全或其他问题.最多,智能指针将为您节省一两行代码.并且唯一具有正确语义的是boost::scoped_ptr,在这种情况下我认为它不起作用.(IIRC,它需要一个完整的类型才能被实例化,但我可能是错的.)

pimpl习语的一个重要方面是它的使用应该对客户透明; 该类的行为应该与它是经典实现的完全相同.这意味着要么禁止复制和赋值,要么实现深层复制,除非该类是不可变的(没有非const成员函数).通常的智能指针都没有实现深拷贝; 当然,你可以实现一个,但是每当副本发生时它可能仍然需要一个完整的类型,这意味着你仍然必须提供一个用户定义的复制构造函数和赋值运算符(因为它们不能是内联的).鉴于此,使用智能指针可能不值得.

一个例外是对象是不可变的.在这种情况下,副本是否深入并不重要,并shared_ptr 完全处理这种情况.


Nim*_*Nim 5

当你使用a shared_ptr(例如在容器中,然后查看它并按返回它)时,你不会导致它指向的对象的副本,只是带有引用计数的指针的副本.

这意味着如果从多个点修改基础对象,则会影响同一实例上的更改.这正是它的设计目标,所以不是一些反模式!

当传递一个shared_ptr(如评论所说)时,最好通过const引用并在需要的地方复制(通过递增引用计数).至于退货,逐案.

  • 这是一个很好的建议,但我认为"总是通过价值回归"有点强烈.首选是通过const引用或右值引用.通过简单,可修改的参考文件,有时甚至是合适的. (2认同)
  • @Johann Gerell:不,你是__not__应该按值传递shared_ptr.你可以,但这不是推荐的方式.推荐的方法是通过const引用传递它们.这比复制shared_ptr更有效,也绝对安全,因为根据shared_ptr文档,同一个shared_ptr实例的并发读取是可以的. (2认同)