何时使用shared_ptr以及何时使用原始指针?

时间:2011-10-05 06:56:48

标签: c++ smart-pointers

class B;

class A
{
public:
    A ()
        : m_b(new B())
    {
    }

    shared_ptr<B> GimmeB ()
    {
        return m_b;
    }

private:
    shared_ptr<B> m_b;
};

假设B是一个在语义上不应该存在于A的生命周期之外的类,也就是说,B对于它自身存在是完全没有意义的。 GimmeB应该返回shared_ptr<B>还是B*

一般来说,完全避免在C ++代码中使用原始指针代替智能指针是一种好习惯吗?

我认为shared_ptr只应在明确转让或共享所有权时使用,我认为除了函数分配一些内存,用一些数据填充它之外,这种情况很少见,并返回它,并且调用者和被调用者之间理解前者现在对该数据“负责”。

10 个答案:

答案 0 :(得分:62)

我认为你的分析是完全正确的。在这种情况下,如果保证对象永远不为null,我也会返回一个裸B*,甚至是[const] B&

我有时间仔细阅读智能指针,我找到了一些指导方针,告诉我在很多情况下该怎么做:

  • 如果您返回一个其生命周期由调用者管理的对象,请返回std::unique_ptr。如果需要,呼叫者可以将其分配给std::shared_ptr
  • 返回std::shared_ptr实际上是非常罕见的,当它有意义时,通常很明显:你向调用者表明它将延长指向对象的生命周期超过对象的生命周期最初维护资源。从工厂返回共享指针也不例外:你必须这样做,例如。当您使用std::enable_shared_from_this
  • 您很少需要std::weak_ptr,除非您想了解lock方法。这有一些用途,但它们很少见。在您的示例中,如果A对象的生命周期从调用者的角度来看不是确定性的,那么这将是需要考虑的事情。
  • 如果返回对调用者无法控制其生命周期的现有对象的引用,则返回裸指针或引用。通过这样做,您告诉调用者存在一个对象,并且她不必处理它的生命周期。如果您不使用nullptr值,则应返回引用。

答案 1 :(得分:23)

问题“我应该何时使用shared_ptr以及何时应该使用原始指针?”有一个非常简单的答案:

  • 如果您不想将任何所有权附加到指针,请使用原始指针。这项工作通常也可以通过参考完成。原始指针也可用于某些低级代码(例如用于实现智能指针或实现容器)。
  • 如果您想要对象的唯一所有权,请使用unique_ptrscope_ptr。这是最有用的选项,应该在大多数情况下使用。通过简单地直接创建对象,而不是使用指针,也可以表示独特的所有权(这比使用unique_ptr更好,如果可以的话)。
  • 当您想要共享指针所有权时,请使用shared_ptrintrusive_ptr。这可能是混乱和低效的,并且通常不是一个好的选择。共享所有权在某些复杂的设计中很有用,但一般应该避免使用,因为它会导致代码难以理解。

shared_ptr执行与原始指针完全不同的任务,shared_ptr和原始指针都不是大多数代码的最佳选择。

答案 2 :(得分:10)

以下是一条经验法则:

  • 当没有传输或共享所有权引用或普通指针足够好时。 (普通指针比引用更灵活。)
  • 当转让所有权但没有共享所有权时,std::unique_ptr<>是一个不错的选择。通常是工厂功能的情况。
  • 当共享所有权时,std::shared_ptr<>boost::intrusive_ptr<>是一个很好的用例。

最好避免共享所有权,部分原因是它们在复制方面最贵,std::shared_ptr<>占用普通指针的两倍,但最重要的是,因为它们有利于设计不良没有明确的所有者,这反过来导致了一个无法破坏的物体的毛球,因为它们彼此拥有共享的指针。

最好的设计是建立明确的所有权并且是分层的,因此理想情况下,根本不需要智能指针。例如,如果有一个工厂创建唯一对象或返回现有对象,那么工厂拥有它创建的对象是有意义的,只需在关联容器(例如std::unordered_map)中按值保留它们,它可以返回普通指针或对其用户的引用。该工厂必须具有在其第一个用户之前开始并在其最后一个用户(分层属性)之后结束的生命周期,以便用户不可能具有指向已经销毁的对象的指针。

答案 3 :(得分:6)

如果你不希望GimmeB()的被调用者能够通过在A实例死后保留ptr的副本来延长指针的生命周期,那么你绝对不应该返回shared_ptr。

如果被调用者不应该长时间保持返回的指针,即A指针的生命周期没有在指针之前到期的风险,则原始指针会更好。但即使是更好的选择也只是使用引用,除非有充分的理由使用实际的原始指针。

最后,如果返回的指针在A实例的生命周期到期后可以存在,但是你不希望指针本身延长B的生命周期,那么你可以返回一个weak_ptr,你可以用来测试它是否仍然存在。

最重要的是,通常比使用原始指针更好的解决方案。

答案 4 :(得分:3)

我同意您的观点,shared_ptr最好在显式共享资源时使用,但是还有其他类型的智能指针可用。

在您的确切情况下:为什么不返回参考?

指针表明数据可能为空,但是B中总会有A,因此它永远不会为空。引用声明了这种行为。

话虽这么说,我看到人们主张即使在非共享环境中使用shared_ptr,并提供weak_ptr句柄,其想法是“保护”应用程序并避免过时的指针。不幸的是,由于您可以从shared_ptr恢复weak_ptr(并且这是实际操作数据的唯一方法),因此即使不是这样,它仍然是共享所有权。

注意:shared_ptr存在一个微妙的错误,默认情况下A的副本将与原始版本共享B,除非您明确编写了复制构造函数和一个复制赋值运算符。当然你不会在A中使用原始指针来保存B,你会不会:)?


当然,另一个问题是你是否真的需要这样做。良好设计的原则之一是封装。实现封装:

  

您不得将句柄返回到您的内部(请参阅Law of Demeter)。

所以也许你的问题的真正答案是,不应该泄露B的引用或指针,而应该只通过A的界面进行修改。

答案 5 :(得分:2)

一般来说,我会尽量避免使用原始指针,因为它们含有非常模糊的含义 - 你可能不得不释放指针,但也许不会,只有人工阅读和写入的文档会告诉你这是什么情况。文档总是糟糕,过时或被误解。

如果所有权是个问题,请使用智能指针。如果没有,我会在可行的情况下使用参考。

答案 6 :(得分:2)

  1. 您在A的建设中分配B.
  2. 你说B不应该在外面坚持下去 这两个都指向B是A的成员并且只是返回一个引用访问器。你是否过度工程?

答案 7 :(得分:2)

我发现C ++核心指南为这个问题提供了一些非常有用的提示:

使用原始指针(T *)或更智能的指针取决于谁拥有该对象(其负责释放obj的内存)。

  

拥有:

smart pointer, owner<T*>
     

不拥有:

T*, T&, span<>

所有者&lt;&gt ;, span&lt;&gt;在Microsoft GSL库中定义

这是经验法则:

1)永远不要使用原始指针(或非自己的类型)来传递所有权

2)智能指针只应在所有权语义出现时使用

3)T *或所有者指定一个单独的对象(仅限)

4)使用vector / array / span作为数组

5)据我所知,shared_ptr通常在你不知道谁将释放obj时使用,例如,一个obj被多线程使用

答案 8 :(得分:1)

最好避免使用原始指针,但不能只用shared_ptr替换所有内容。在该示例中,您的类的用户将认为可以将B的生命周期延长到超过A的生命周期,并且可能出于他们自己的原因决定将返回的B对象保留一段时间。你应该返回weak_ptr,或者,如果在销毁A时绝对不存在B,则引用B或只是原始指针。

答案 9 :(得分:0)

当你说:“让我们说B是一个在语义上不应该存在于A的生命周期之外的类”

这告诉我B应该逻辑在没有A的情况下不存在,但实际存在的是什么? 如果你可以肯定没有人会尝试在A dtors之后使用* B而不是原始指针就可以了。否则,更智能的指针可能是合适的。

当客户有一个指向A的直接指针时,你必须相信它们会适当地处理它;不要尝试dtoring it等。