何时使用shared_ptr,何时使用原始指针?

发布于 2024-12-07 20:52:59 字数 557 浏览 4 评论 0原文

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

驱逐舰岛风号 2024-12-14 20:52:59

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

在花了一些时间仔细研究智能指针后,我得出了一些指导原则,它们告诉我在许多情况下该怎么做:

  • 如果返回一个其生命周期由调用者管理的对象,请返回 std::unique_ptr。如果需要,调用者可以将其分配给 std::shared_ptr
  • 返回 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:

  • If you return an object whose lifetime is to be managed by the caller, return std::unique_ptr. The caller can assign it to a std::shared_ptr if it wants.
  • Returning 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 use std::enable_shared_from_this.
  • You very rarely need std::weak_ptr, except when you want to make sense of the lock method. This has some uses, but they are rare. In your example, if the lifetime of the A object was not deterministic from the caller's point of view, this would have been something to consider.
  • If you return a reference to an existing object whose lifetime the caller cannot control, then return a bare pointer or a reference. By doing so, you tell the caller that an object exists and that she doesn't have to take care of its lifetime. You should return a reference if you don't make use of the nullptr value.
怪我入戏太深 2024-12-14 20:52:59

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

  • 当您不想将任何所有权附加到指针时,请使用原始指针。这项工作通常也可以通过参考来完成。原始指针也可以用在一些低级代码中(例如用于实现智能指针或实现容器)。
  • 当您想要对象的唯一所有权时,请使用 unique_ptrscope_ptr。这是最有用的选项,并且应该在大多数情况下使用。唯一所有权也可以通过直接创建一个对象来表达,而不是使用指针(这甚至比使用 unique_ptr 更好,如果可以的话)。
  • 当您想要共享指针的所有权时,请使用 shared_ptrintrusive_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:

  • Use raw pointers when you do not want to have any ownership attached to the pointer. This job can also often be done with references. Raw pointers can also be used in some low level code (such as for implementing smart pointers, or implementing containers).
  • Use unique_ptr or scope_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 a unique_ptr, if it can be done).
  • Use shared_ptr or intrusive_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_ptrs perform a totally different task from raw pointers, and neither shared_ptrs nor raw pointers are the best option for the majority of code.

煮茶煮酒煮时光 2024-12-14 20:52:59

以下是一个很好的经验法则:

  • 当没有共享所有权转移时,引用或普通指针就足够了。 (普通指针比引用更灵活。)
  • 当存在所有权转移但没有共享所有权时,std::unique_ptr 是一个不错的选择。工厂功能经常出现这种情况。
  • 当存在共享所有权时,这是 std::shared_ptrboost::intrusive_ptr 的良好用例。

最好避免共享所有权,部分原因是它们在复制方面成本最高,并且 std::shared_ptr<> 占用普通指针存储空间的两倍,但最重要的是,因为它们有利于没有明确所有者的糟糕设计,这反过来又会导致无法销毁的对象的毛球,因为它们彼此持有共享指针。

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

The following is a good rule of thumb:

  • When there is no transfer of shared ownership references or plain pointers are good enough. (Plain pointers are more flexible than references.)
  • When there is transfer of ownership but no shared ownership then std::unique_ptr<> is a good choice. Often the case with factory functions.
  • When there is shared ownership, then it is a good use case for std::shared_ptr<> or boost::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.

你在我安 2024-12-14 20:52:59

如果您不希望 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.

过潦 2024-12-14 20:52:59

我同意您的观点,即当发生显式资源共享时最好使用 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 your A, 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 giving weak_ptr handles, with the idea of "securing" the application and avoiding stale pointers. Unfortunately, since you can recover a shared_ptr from the weak_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 of A will share the same B 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 in A to hold a B, 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:

You shall not return handles to your internals (see Law of Demeter).

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 through A's interface.

(り薆情海 2024-12-14 20:52:59

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

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

拥有:

智能指针,所有者

不属于自己:

T*、T&、跨度>>

所有者<>,跨度<>在 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).

own :

smart pointer, owner<T*>

not own:

T*, T&, span<>

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

罪#恶を代价 2024-12-14 20:52:59

一般来说,我会尽可能避免使用原始指针,因为它们的含义非常模糊 - 您可能必须释放指针对象,但也许不需要,并且只有人类阅读和编写的文档才能告诉您情况是什么。而且文档总是不好的、过时的或被误解的。

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

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.

缪败 2024-12-14 20:52:59
  1. 您在 A 的构造时分配 B。
  2. 您说 B 不应该在 As 生命周期之外持续存在。
    这两者都表明 B 是 A 的成员,并且 a 只是返回引用访问器。你是不是过度设计了?
  1. You allocate B at constuction of A.
  2. You say B shouldn't persist outside As lifetime.
    Both these point to B being a member of A and a just returning a reference accessor. Are you overengineering this?
寂寞清仓 2024-12-14 20:52:59

避免使用原始指针是一种很好的做法,但您不能只用 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 a weak_ptr, or, if B absolutely cannot exist when A is destroyed, a reference to B or simply a raw pointer.

椒妓 2024-12-14 20:52:59

当你说:“假设 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.

一紙繁鸢 2024-12-14 20:52:59

如果 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 use std::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 the std::weak_ptr and get a std::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 because std::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 a std::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 a std::shared_ptr as suggested in another answer. There is only a move constructor for assigning a std::unique_ptr to a std::shared_ptr. In that case the std::unique_ptr will be set to null and the std::shared_ptr will point to the object. The reason is that std::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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文