何时使用shared_ptr以及何时使用原始指针?
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
应该只在明确的所有权转移或共享时才使用,除了函数分配一些内存,填充一些数据并返回它的情况之外,我认为这是很less见的,主叫方和被叫方之间的理解是,前者现在对这些数据负责。
我认为你的分析是非常正确的。 在这种情况下,我也会返回一个空B*
,甚至是一个[const] B&
如果这个对象保证永远不为null。
经过一段时间仔细阅读指针,我得到了一些指导,告诉我在很多情况下要做什么:
- 如果您返回的对象的生命周期将由调用方pipe理,则返回
std::unique_ptr
。 调用者可以将其分配给std::shared_ptr
如果需要的话)。 - 返回
std::shared_ptr
实际上是相当罕见的,而且当它有意义时,通常很明显:向调用者指出它将延长指向对象的生命期,超出原始维护资源的对象的生命周期。 从工厂返回共享指针也不例外:例如,你必须这样做。 当你使用std::enable_shared_from_this
。 - 你很less需要
std::weak_ptr
,除非你想了解lock
方法。 这有一些用途,但它们很less见。 在你的例子中,如果A
对象的生命周期从调用者的angular度来看并不是确定性的,那么这是可以考虑的。 - 如果你返回一个对调用者无法控制其生命周期的现有对象的引用,则返回一个空指针或引用。 通过这样做,你告诉调用者一个对象存在,并且她不必关心它的生命周期。 如果不使用
nullptr
值,则应该返回一个引用。
“什么时候应该使用shared_ptr
,什么时候应该使用原始指针? 有一个非常简单的答案:
- 如果您不想将任何所有权连接到指针,请使用原始指针。 这项工作通常也可以通过参考来完成。 原始指针也可以用在一些低级代码中(比如实现智能指针或实现容器)。
- 当您想要对象的唯一所有权时,请使用
unique_ptr
或scope_ptr
。 这是最有用的选项,应该在大多数情况下使用。 独特的所有权也可以通过直接创build一个对象来表示,而不是使用指针(如果可以的话,这比使用unique_ptr
更好)。 - 当你希望共享指针的所有权时,使用
shared_ptr
或intrusive_ptr
。 这可能是混乱和低效率,往往不是一个好的select。 共享所有权在一些复杂的devise中可能是有用的,但一般应避免,因为这会导致代码难以理解。
shared_ptr
从原始指针执行完全不同的任务, shared_ptr
和raw指针都不是大多数代码的最佳select。
如果你不希望GimmeB()的被调用者能够通过在A的实例死后保留ptr的副本来延长指针的生命期,那么你肯定不应该返回一个shared_ptr。
如果被调用者不应该长时间保存返回的指针,也就是说,在指针之前没有A的生命期实例到期的风险,那么原始指针会更好。 但是,即使是更好的select,也只是使用引用,除非有充分的理由使用实际的原始指针。
最后在返回的指针可以在A实例的生命期到期之后存在的情况下,但是你不希望指针本身延长B的生命期,那么你可以返回一个weak_ptr,你可以用它来testing是否仍然存在。
底线是通常比使用原始指针更好的解决scheme。
以下是一个很好的经验法则:
- 当没有传输或共享所有权引用或纯指针是不够的。 (普通指针比引用更灵活。)
- 当拥有所有权但没有共享所有权时,则
std::auto_ptr<>
或std::unique_ptr<>
是一个不错的select。 通常情况下,工厂function。 - 当共享所有权时,
std::shared_ptr<>
或boost::intrusive_ptr<>
是一个很好的用例。
我同意你的看法, shared_ptr
是最好的时候显式共享资源发生,但也有其他types的智能指针可用。
在你的确切情况下:为什么不返回一个参考?
一个指针表明数据可能是空的,但是这里在你的A
总会有一个B
,所以它永远不会为空。 引用声明了这种行为。
这就是说,我曾经看到人们提倡使用shared_ptr
即使在非共享的环境中,并给予weak_ptr
句柄,也就是“保护”应用程序并避免陈旧的指针。 不幸的是,由于你可以从weak_ptr
恢复一个shared_ptr
(这是实际操纵数据的唯一方法),即使它不是本意的,它仍然是共享所有权。
注意: shared_ptr
有一个微妙的错误,除非你明确地写了一个拷贝构造函数和一个拷贝赋值操作符,否则默认情况下, A
的拷贝将与原始拷贝共享相同的B
当然,你不会在A
使用一个原始指针来保存一个B
,你会:)?
当然,另一个问题是你是否真的需要这样做。 良好的devise原则之一是封装 。 为了实现封装:
你不应该回到你的内部处理(见德米特法 )。
所以也许你的问题的真正答案是,而不是给B
的引用或指针,它应该只能通过A
的界面进行修改。
一般来说,我会尽可能地避免使用原始指针,因为它们的含义非常模糊 – 您可能不得不释放指针,但也许不会,只有人类阅读和书写的文档才会告诉您是什么情况。 而且文件总是不好,过时或被误解。
如果所有权是一个问题,请使用智能指针。 如果不是的话,我会尽可能使用一个参考。
- 您在构buildA时分配B.
- 你说B不应该一辈子坚持下去。
这两个都指向B是A的成员,并且只是返回一个引用访问器。 你在过度工作吗?
避免使用原始指针是一个很好的做法,但是不能只用shared_ptr
replace所有的指针。 在这个例子中,你的类的用户会认为延长B的生命期超过了A的生命周期是可以的,并且可能由于自己的原因决定将返回的B对象保留一段时间。 您应该返回一个weak_ptr
,或者,如果B在A被销毁时绝对不能存在,则引用B或者仅仅是一个原始指针。
当你说:“让我们说B是一个在A的生命周期之外语义上不应该存在的类”
这告诉我B在逻辑上不应该存在没有A,但物理存在呢? 如果你能确定没有人会尝试使用a * B之后的A,否则可能原始指针将罚款。 否则,更聪明的指针可能是合适的。
当客户有一个直接指向A的指针时,你必须相信他们会适当地处理它; 不要试图等等
我发现C ++核心指南为这个问题提供了一些非常有用的提示:
使用原始指针(T *)或更智能的指针取决于谁拥有该对象(其释放obj的内存的责任)。
拥有 :
smart pointer, own<T*>
不是自己的:
T*, T&, span<>
自己的<>,span <>在Microsoft GSL库中定义
这里是经验法则:
1)从不使用原始指针(或不是自己的types)来传递所有权
2)智能指针只能在所有权语义被使用时使用
3)T *或所有者指定一个单独的对象(仅)
4)使用vector / array / span作为数组
5)对于我不熟悉的人来说,shared_ptr通常用在你不知道谁将释放obj的情况下,例如,一个obj被multithreading使用