54

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

我知道这样std::unique_ptr效率更高,但这对我来说不是什么大问题,因为这些对象无论如何都比较重,所以std::shared_ptrover的成本std::unique_ptr相对较小。

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

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

它是否正确?

4

4 回答 4

38

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

绝对unique_ptrscoped_ptr

Pimpl不是一种模式,而是一种习语,它处理编译时依赖和二进制兼容性。它不应影响对象的语义,尤其是在其复制行为方面。

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

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

它不是反模式,实际上是一种模式:Aliasing。您已经在 C++ 中使用了它,并带有裸指针和引用。shared_ptr提供额外的“安全”措施以避免死引用,但代价是额外的复杂性和新问题(注意会造成内存泄漏的循环)。


与粉刺无关

我知道unique_ptr效率更高,但这对我来说不是什么大问题,因为这些对象无论如何都是相对重量级的,所以shared_ptrover的成本unique_ptr相对较小。

如果您可以考虑某些状态,您可能需要查看Flyweight模式。

于 2011-04-07T07:11:25.327 回答
12

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

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

一个例外是对象是不可变的。在这种情况下,无论副本是否深,都可以shared_ptr 完全处理这种情况。

于 2011-04-07T08:23:28.593 回答
6

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

这意味着如果您从多个点修改底层对象,那么您会影响同一实例上的更改。这正是它的设计目的,所以不是一些反模式

当传递 a 时shared_ptr(如评论所说),最好通过 const 引用传递并在需要的地方复制(通过增加引用计数)。至于退货,视情况而定。

于 2011-04-07T06:39:52.657 回答
0

是的,请使用它们。简单地说,shared_ptr 是智能指针的一种实现。unique_ptr 是自动指针的实现:

于 2017-06-12T23:32:12.260 回答