何时使用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*
?
一般来说,这样好吗练习完全避免在 C++ 代码中使用原始指针来代替智能指针?
我认为 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;
};
Let's say B is a class that semantically should not exist outside of the lifetime of A, i.e., it makes absolutely no sense for B to exist by itself. Should GimmeB
return a shared_ptr<B>
or a B*
?
In general, is it good practice to completely avoid using raw pointers in C++ code, in lieu of smart pointers?
I am of the opinion that shared_ptr
should only be used when there is explicit transfer or sharing of ownership, which I think is quite rare outside of cases where a function allocates some memory, populates it with some data, and returns it, and there is understanding between the caller and the callee that the former is now "responsible" for that data.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
我认为你的分析非常正确。在这种情况下,如果保证对象永远不为空,我也会返回一个空的
B*
,甚至返回一个[const] B&
。在花了一些时间仔细研究智能指针后,我得出了一些指导原则,它们告诉我在许多情况下该怎么做:
std::shared_ptr
。std::enable_shared_from_this
时。std::weak_ptr
,除非您想要理解lock
方法。这有一些用途,但很少见。在您的示例中,如果从调用者的角度来看A
对象的生命周期不是确定性的,则需要考虑这一点。nullptr
值,则应该返回一个引用。Your analysis is quite correct, I think. In this situation, I also would return a bare
B*
, or even a[const] B&
if the object is guaranteed to never be null.Having had some time to peruse smart pointers, I arrived at some guidelines which tell me what to do in many cases:
std::unique_ptr
. The caller can assign it to astd::shared_ptr
if it wants.std::shared_ptr
is actually quite rare, and when it makes sense, it is generally obvious: you indicate to the caller that it will prolong the lifetime of the pointed-to object beyond the lifetime of the object which was originally maintaining the resource. Returning shared pointers from factories is no exception: you must do this eg. when you usestd::enable_shared_from_this
.std::weak_ptr
, except when you want to make sense of thelock
method. This has some uses, but they are rare. In your example, if the lifetime of theA
object was not deterministic from the caller's point of view, this would have been something to consider.nullptr
value.问题“什么时候应该使用
shared_ptr
以及什么时候应该使用原始指针?”有一个非常简单的答案:unique_ptr
或scope_ptr
。这是最有用的选项,并且应该在大多数情况下使用。唯一所有权也可以通过直接创建一个对象来表达,而不是使用指针(这甚至比使用unique_ptr
更好,如果可以的话)。shared_ptr
或intrusive_ptr
。这可能会造成混乱且效率低下,并且通常不是一个好的选择。共享所有权在某些复杂的设计中可能很有用,但一般应避免,因为它会导致代码难以理解。shared_ptr
执行与原始指针完全不同的任务,并且shared_ptr
和原始指针都不是大多数代码的最佳选择。The question "when should I use
shared_ptr
and when should I use raw pointers?" has a very simple answer:unique_ptr
orscope_ptr
when you want unique ownership of the object. This is the most useful option, and should be used in most cases. Unique ownership can also be expressed by simply creating an object directly, rather than using a pointer (this is even better than using aunique_ptr
, if it can be done).shared_ptr
orintrusive_ptr
when you want shared ownership of the pointer. This can be confusing and inefficient, and is often not a good option. Shared ownership can be useful in some complex designs, but should be avoided in general, because it leads to code which is hard to understand.shared_ptr
s perform a totally different task from raw pointers, and neithershared_ptr
s nor raw pointers are the best option for the majority of code.以下是一个很好的经验法则:
std::unique_ptr
是一个不错的选择。工厂功能经常出现这种情况。std::shared_ptr
或boost::intrusive_ptr
的良好用例。最好避免共享所有权,部分原因是它们在复制方面成本最高,并且
std::shared_ptr<>
占用普通指针存储空间的两倍,但最重要的是,因为它们有利于没有明确所有者的糟糕设计,这反过来又会导致无法销毁的对象的毛球,因为它们彼此持有共享指针。最好的设计是建立明确的所有权并且是分层的,因此,理想情况下根本不需要智能指针。例如,如果有一个工厂创建唯一对象或返回现有对象,则该工厂拥有它创建的对象并仅按值将它们保存在关联容器中(例如
std::unordered_map< /code>),以便它可以向其用户返回普通指针或引用。该工厂的生命周期必须在其第一个用户之前开始并在其最后一个用户之后结束(层次结构属性),以便用户不可能拥有指向已销毁对象的指针。
The following is a good rule of thumb:
std::unique_ptr<>
is a good choice. Often the case with factory functions.std::shared_ptr<>
orboost::intrusive_ptr<>
.It is best to avoid shared ownership, partly because they are most expensive in terms of copying and
std::shared_ptr<>
takes double of the storage of a plain pointer, but, most importantly, because they are conducive for poor designs where there are no clear owners, which, in turn, leads to a hairball of objects that cannot destroy because they hold shared pointers to each other.The best design is where clear ownership is established and is hierarchical, so that, ideally, no smart pointers are required at all. For example, if there is a factory that creates unique objects or returns existing ones, it makes sense for the factory to own the objects it creates and just keep them by value in an associative container (such as
std::unordered_map
), so that it can return plain pointers or references to its users. This factory must have lifetime that starts before its first user and ends after its last user (the hierarchical property), so that users cannot possible have a pointer to an already destroyed object.如果您不希望 GimmeB() 的被调用者能够在 A 实例死亡后通过保留 ptr 的副本来延长指针的生命周期,那么您绝对不应该返回共享_ptr。
如果被调用者不应该长时间保留返回的指针,即不存在 A 实例的生命周期在指针之前到期的风险,那么原始指针会更好。但即使更好的选择也只是使用引用,除非有充分的理由使用实际的原始指针。
最后,如果返回的指针可以在A实例的生存期到期后存在,但您不希望指针本身延长B的生存期,那么您可以返回一个weak_ptr,您可以用它来测试它是否仍然存在。
最重要的是,通常有比使用原始指针更好的解决方案。
If you don't want the callee of GimmeB() to be able to extend the lifetime of the pointer by keeping a copy of the ptr after the instance of A dies, then you definitely should not return a shared_ptr.
If the callee is not supposed to keep the returned pointer for long periods of time, i.e. there's no risk of the instance of A's lifetime expiring before the pointer's, then raw pointer would be better. But even a better choice is simply to use a reference, unless there's a good reason to use an actual raw pointer.
And finally in the case that the returned pointer can exist after the lifetime of the A instance has expired, but you don't want the pointer itself extend the lifetime of the B, then you can return a weak_ptr, which you can use to test whether it still exists.
The bottom line is that there's usually a nicer solution than using a raw pointer.
我同意您的观点,即当发生显式资源共享时最好使用
shared_ptr
,但是还有其他类型的智能指针可用。在您的具体情况下:为什么不返回引用?
指针表明数据可能为空,但是这里
A
中总会有一个B
,因此它永远不会为空。该参考文献断言了这种行为。话虽这么说,我看到有人提倡即使在非共享环境中也使用
shared_ptr
,并提供weak_ptr
句柄,其想法是“保护”应用程序并避免陈旧的指针。不幸的是,由于您可以从weak_ptr
恢复shared_ptr
(并且这是实际操作数据的唯一方法),因此这仍然是共享所有权,即使它不是故意的成为。注意:
shared_ptr
存在一个微妙的错误,默认情况下,A
的副本将与原始版本共享相同的B
,除非您显式编写一个复制构造函数和一个复制赋值运算符。当然,您不会在A
中使用原始指针来保存B
,不是吗:)?当然,另一个问题是您是否真的需要这样做。良好设计的原则之一是封装。实现封装:
因此,也许您问题的真正答案是,不应放弃对 B 的引用或指针,而应仅通过 A 的接口对其进行修改。
I agree with your opinion that
shared_ptr
is best used when explicit sharing of resources occurs, however there are other types of smart pointers available.In your precise case: why not return a reference ?
A pointer suggests that the data might be null, however here there will always be a
B
in yourA
, thus it will never be null. The reference asserts this behavior.That being said, I have seen people advocating the use of
shared_ptr
even in non-shared environments, and givingweak_ptr
handles, with the idea of "securing" the application and avoiding stale pointers. Unfortunately, since you can recover ashared_ptr
from theweak_ptr
(and it is the only way to actually manipulate the data), this is still shared ownership even if it was not meant to be.Note: there is a subtle bug with
shared_ptr
, a copy ofA
will share the sameB
as the original by default, unless you explicitly write a copy constructor and a copy assignment operator. And of course you would not use a raw pointer inA
to hold aB
, would you :) ?Of course, another question is whether you actually need to do so. One of the tenets of good design is encapsulation. To achieve encapsulation:
so perhaps the real answer to your question is that instead of giving away a reference or pointer to
B
, it should only be modified throughA
's interface.我发现 C++ 核心指南针对这个问题给出了一些非常有用的提示:
使用原始指针(T*)或更智能的指针取决于谁拥有该对象(谁负责释放 obj 的内存)。
所有者<>,跨度<>在 Microsoft GSL 库中定义,
这里是经验规则:
1) 切勿使用原始指针(或非自己的类型)来传递所有权
2) 智能指针仅应在需要所有权语义时使用
3) T* 或所有者指定个人object(only)
4)数组使用vector/array/span
5)据我了解,shared_ptr通常在不知道谁会释放obj的情况下使用,例如多线程使用一个obj
I found that the C++ Core Guidelines give some very useful hints for this question:
To use raw pointer(T*) or smarter pointer depends on who owns the object (whose responsibility to release memory of the obj).
owner<>, span<> is defined in Microsoft GSL library
here is the rules of thumb:
1) never use raw pointer(or not own types) to pass ownership
2) smart pointer should only be used when ownership semantics are intended
3) T* or owner designate a individual object(only)
4) use vector/array/span for array
5) To my undetstanding, shared_ptr is usually used when you don't know who will release the obj, for example, one obj is used by multi-thread
一般来说,我会尽可能避免使用原始指针,因为它们的含义非常模糊 - 您可能必须释放指针对象,但也许不需要,并且只有人类阅读和编写的文档才能告诉您情况是什么。而且文档总是不好的、过时的或被误解的。
如果所有权是一个问题,请使用智能指针。如果没有,我会在可行的情况下使用参考。
Generally, I would avoid using raw pointers as far as possible since they have very ambiguous meaning - you might have to deallocate the pointee, but maybe not, and only human-read and -written documentation tells you what the case is. And documentation is always bad, outdated or misunderstood.
If ownership is an issue, use a smart pointer. If not, I'd use a reference if practicable.
这两者都表明 B 是 A 的成员,并且 a 只是返回引用访问器。你是不是过度设计了?
Both these point to B being a member of A and a just returning a reference accessor. Are you overengineering this?
避免使用原始指针是一种很好的做法,但您不能只用
shared_ptr
替换所有内容。在示例中,您的类的用户会认为可以将 B 的生命周期延长到 A 的生命周期之外,并且可能会出于自己的原因决定将返回的 B 对象保留一段时间。您应该返回一个weak_ptr,或者,如果当A被销毁时B绝对不存在,则返回对B的引用或只是一个原始指针。It is good practice to avoid using raw pointers, but you can not just replace everything with
shared_ptr
. In the example, users of your class will assume that it's ok to extend B's lifetime beyond that of A's, and may decide to hold the returned B object for some time for their own reasons. You should return aweak_ptr
, or, if B absolutely cannot exist when A is destroyed, a reference to B or simply a raw pointer.当你说:“假设 B 是一个在语义上不应该存在于 A 的生命周期之外的类”时,
这告诉我 B 在没有 A 的情况下应该逻辑上不存在,但是物理上存在呢?
如果您可以确定没有人会在 A dtors 之后尝试使用 *B,那么也许原始指针就可以了。否则,更智能的指针可能更合适。
当客户有一个指向 A 的直接指针时,你必须相信他们会适当地处理它;不要尝试dtoring它等等。
When you say: "Let's say B is a class that semantically should not exist outside of the lifetime of A"
This tells me B should logically not exist without A, but what about physically existing?
If you can be sure no one will try using a *B after A dtors than perhaps a raw pointer will be fine. Otherwise a smarter pointer may be appropriate.
When clients have a direct pointer to A you have to trust they'll handle it appropriately; not try dtoring it etc.
如果 B 不应该存在于 A 的生命周期之外,那么问题是是否应该在 A 的接口中公开 B。
因为一旦 A 结束其生命周期,给出一个不再有意义的指针就很容易出错。
但是假设您有充分的理由在 A 的接口中公开 B:使用 std::shared_ptr 允许调用者延长 B 的生命周期。使用原始指针不会延长 B 的生命周期,但是当 A 结束其生命周期并删除 B 后尝试访问 B 时,将导致无效的内存访问。
因此,如果调用者尝试在 A 的生命周期之外访问 B,则使用 std::shared_ptr 可能仍然有意义,至少可以防止访问无效内存。
您可能会返回 std::weak_ptr 并在内部使用
std::shared_ptr
向该类的用户提示 B 的生命周期不会超出 A 的生命周期。但这并不是强制执行的。毕竟,您可以锁定 std::weak_ptr 并从中获取 std::shared_ptr 。您还可以返回一个引用或 const-reference 来指示 A 的客户端不应保留指向 B 的指针。但这并不强制执行,因为您可以使用运算符&从引用创建指针。
在这种情况下,返回
std::unique_ptr
值没有意义,因为std::unique_ptr
没有复制构造函数。它只有一个转移所有权的移动构造函数。毕竟,std::unique_ptr 强制规定任何时候只有一个指针可以指向该对象。因此,使用 std::unique_ptr ,您只能提供一个接口,将对象 B 的独占所有权转移到类 A 之外。这与您在此处尝试实现的目标相反。正如另一个答案中所建议的,也不可能将
std::unique_ptr
分配给std::shared_ptr
。只有一个移动构造函数用于将std::unique_ptr
分配给std::shared_ptr
。在这种情况下,std::unique_ptr
将被设置为null
并且std::shared_ptr
将指向该对象。原因是 std::unique_ptr 强制并假设它是指向其对象的唯一指针。您可以返回对内部使用的
std::unique_ptr
的 const 引用。在这种情况下,调用者无法直接将指针分配给其他智能指针,因为 const 引用会阻止使用移动构造函数。然而,它也不会强制执行它,因为仍然可以提取原始指针并对其执行任何操作。If B shouldn't exist outside of the lifetime of A, the question is whether you should expose B at all in the interface of A.
Because it's quite error prone to give out a pointer that stops making sense once A ends its lifetime.
But assuming that you have a good reason for exposing B in the interface of A: Using
std::shared_ptr
allows the caller to extend the lifetime of B. Using a raw pointer will not extend the lifetime of B, but will lead to an invalid memory access when trying to access B after B has been deleted when A ended its lifetime.So, using
std::shared_ptr
may still make sense to at least prevent the access of invalid memory if the caller tries to access B beyond the lifetime of A.You might return
std::weak_ptr
and usestd::shared_ptr
internally to give users of the class a hint that the lifetime of B is not to be extended beyond the lifetime of A. But it's not enforced. After all, you can lock thestd::weak_ptr
and get astd::shared_ptr
out of it.You can also return a reference or
const
-reference to indicate that the client of A is not supposed to keep a pointer to B around. But it's not enforced because you can create a pointer from a reference using operator&.A value of
std::unique_ptr
doesn't make sense to return in this case becausestd::unique_ptr
doesn't have a copy constructor. It only has a move-constructor which transfers ownership. After all,std::unique_ptr
enforces that only one pointer can point to the object at any time. So, with astd::unique_ptr
you could only provide an interface that transfers the exclusive ownership of object B out of class A. Which is the opposite of what you try to achieve here.It's also not possible to assign an
std::unique_ptr
to astd::shared_ptr
as suggested in another answer. There is only a move constructor for assigning astd::unique_ptr
to astd::shared_ptr
. In that case thestd::unique_ptr
will be set tonull
and thestd::shared_ptr
will point to the object. The reason is thatstd::unique_ptr
enforces and assumes that it is the only pointer pointing to its object.You may return a const reference to a
std::unique_ptr
that's used internally. In that case, the caller cannot directly assign the pointer to some other smart pointer because a const reference prevents using the move constructor. However it also doesn't enforce it because it's still possible to extract the raw pointer and do whatever with it.