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
,我认为这种情况非常罕见,除非函数分配一些内存,用一些数据填充它并返回它,并且调用者和被调用者之间理解前者现在"负责"该数据。
我认为你的分析非常正确。在这种情况下,我也会返回一个裸B*
,如果保证对象永远不会为空,甚至返回一个[const] B&
。
在花了一些时间仔细阅读智能指针后,我得出了一些指导方针,这些指导方针告诉我在许多情况下该怎么做:
- 如果返回的对象生存期将由调用方管理,则返回
std::unique_ptr
。如果需要,调用方可以将其分配给std::shared_ptr
。 - 返回
std::shared_ptr
实际上非常罕见,当它有意义时,通常是显而易见的:您向调用方指示它将延长指向对象的生存期,超过最初维护资源的对象的生存期。从工厂返回共享指针也不例外:您必须这样做,例如。当您使用std::enable_shared_from_this
. - 你很少需要
std::weak_ptr
,除非你想理解lock
方法。这有一些用途,但很少见。在您的示例中,如果从调用方的角度来看,A
对象的生存期不是确定性的,那么需要考虑这一点。 - 如果返回对调用方无法控制其生存期的现有对象的引用,则返回裸指针或引用。通过这样做,您可以告诉调用方存在一个对象,并且她不必照顾其生存期。如果不使用
nullptr
值,则应返回引用。
"我什么时候应该使用shared_ptr
,什么时候应该使用原始指针?"这个问题有一个非常简单的答案:
- 当您不想将任何所有权附加到指针时,请使用原始指针。这项工作通常也可以通过参考来完成。原始指针还可以用于某些低级代码(例如用于实现智能指针或实现容器(。
- 当您需要对象的唯一所有权时,请使用
unique_ptr
或scope_ptr
。这是最有用的选项,在大多数情况下应使用。唯一所有权也可以通过简单地直接创建一个对象来表示,而不是使用指针(如果可以的话,这甚至比使用unique_ptr
更好(。 - 当您想要共享指针的所有权时,请使用
shared_ptr
或intrusive_ptr
。这可能会令人困惑且效率低下,并且通常不是一个好的选择。共享所有权在某些复杂的设计中可能很有用,但通常应避免,因为它会导致难以理解的代码。
shared_ptr
执行的任务与原始指针完全不同,shared_ptr
和原始指针都不是大多数代码的最佳选择。
以下是一个很好的经验法则:
- 当没有共享所有权的转让时,引用或普通指针就足够了。(纯指针比引用更灵活。
- 当所有权转让但没有共享所有权时,
std::unique_ptr<>
是一个不错的选择。工厂功能通常就是这种情况。 - 当有共享所有权时,它是
std::shared_ptr<>
或boost::intrusive_ptr<>
的一个很好的用例。
最好避免共享所有权,部分原因是它们在复制方面最昂贵,并且std::shared_ptr<>
占用普通指针存储的两倍,但最重要的是,因为它们有利于没有明确所有者的糟糕设计,这反过来又导致无法破坏的对象毛球,因为它们彼此持有共享指针。
最好的设计是建立明确的所有权并且是分层的,因此,理想情况下,根本不需要智能指针。例如,如果有一个工厂创建唯一对象或返回现有对象,则工厂拥有它创建的对象并按值将它们保存在关联容器(例如 std::unordered_map
(中是有意义的,以便它可以向其用户返回纯指针或引用。此工厂的生存期必须在其第一个用户之前开始,在其最后一个用户(分层属性(之后结束,以便用户无法获得指向已销毁对象的指针。
如果你不希望 GimmeB(( 的被调用者能够在 A 实例死亡后保留 ptr 的副本来延长指针的生存期,那么你绝对不应该返回shared_ptr。
如果被调用方不应该长时间保留返回的指针,即不存在 A 的生命周期在指针之前过期的风险,那么原始指针会更好。但即使是更好的选择也是简单地使用引用,除非有充分的理由使用实际的原始指针。
最后,如果返回的指针可以在 A 实例的生存期到期后存在,但您不希望指针本身延长 B 的生存期,则可以返回一个 weak_ptr,您可以使用它来测试它是否仍然存在。
最重要的是,通常有比使用原始指针更好的解决方案。
我同意您的观点,即在发生显式资源共享时最好使用 shared_ptr
,但是还有其他类型的智能指针可用。
在您的确切情况下:为什么不返回引用?
指针表明数据可能为空,但是此处A
中始终存在B
,因此它永远不会为空。引用断言此行为。
话虽如此,我看到有人提倡即使在非共享环境中也使用shared_ptr
,并提供weak_ptr
句柄,其想法是"保护"应用程序并避免过时的指针。不幸的是,由于您可以从weak_ptr
中恢复shared_ptr
(这是实际操作数据的唯一方法(,因此即使它不是故意的,这仍然是共享所有权。
注意:shared_ptr
有一个微妙的错误,默认情况下,A
的副本将与原始副本共享相同的B
,除非您显式编写复制构造函数和复制赋值运算符。当然,您不会在A
中使用原始指针来保持B
,您会:)吗?
当然,另一个问题是你是否真的需要这样做。良好设计的原则之一是封装。要实现封装,请执行以下操作:
您不得将手柄归还给您的内部(参见得墨忒耳定律(。
所以也许你的问题的真正答案是,与其给出一个引用或指向B
的指针,不如只通过A
的界面进行修改。
一般来说,我会尽可能避免使用原始指针,因为它们的含义非常模糊 - 你可能不得不释放点,但可能不是,只有人类阅读和编写的文档告诉你情况是什么。文档总是不好、过时或被误解。
如果所有权是一个问题,请使用智能指针。如果没有,如果可行,我会使用参考。
- 您在构造 A 时分配 B。
- 你说B不应该在外面坚持一辈子。
这两者都指向 B 是 A 的成员,而 A 只是返回一个引用访问器。你是否过度设计了这个?
避免使用原始指针是一种很好的做法,但你不能只是用 shared_ptr
替换所有内容。在此示例中,类的用户将假定可以将 B 的生存期延长到 A 的生存期之外,并且可能出于自己的原因决定将返回的 B 对象保留一段时间。你应该返回一个weak_ptr
,或者,如果 B 在 A 被销毁时绝对不存在,则返回对 B 的引用或只是一个原始指针。
我发现C++核心指南为这个问题提供了一些非常有用的提示:
使用原始指针 (T*( 或更智能的指针取决于谁拥有对象(谁负责释放 obj 的内存(。
有:
smart pointer, owner<T*>
不拥有:
T*, T&, span<>
所有者<>、span<> 在 GSL 库中定义Microsoft
以下是经验法则:
1(切勿使用原始指针(或不是自己的类型(来传递所有权
2( 智能指针应仅在需要所有权语义时使用
3( T* 或所有者指定单个对象(仅限(
4( 使用向量/数组/跨度作为数组
5(在我看来,shared_ptr通常在你不知道谁会发布obj时使用,例如,一个obj被多线程
当你说:"假设 B 是一个语义上不应该存在于 A 生命周期之外的类">
这告诉我,没有A,B在逻辑上不应该存在,但是物理存在呢?如果您可以确定没有人会在 A dtors 之后尝试使用 *B,那么原始指针可能会很好。 否则,更智能的指针可能是合适的。
当客户端有指向 A 的直接指针时,您必须相信他们会适当地处理它;而不是尝试存储它等。