什么时候使用哪种指针?

发布于 2024-12-24 03:50:22 字数 301 浏览 2 评论 0 原文

好吧,上次我以编写 C++ 为生时,std::auto_ptr 是所有可用的 std 库,而 boost::shared_ptr 则风靡一时。我从未真正研究过 boost 提供的其他智能指针类型。据我所知,C++11 现在提供了 boost 提出的一些类型,但不是全部。

那么有人有一个简单的算法来确定何时使用哪个智能指针吗?最好包括有关哑指针(原始指针,如 T*)和其余 boost 智能指针的建议。 (像这个这样的东西会很棒)。

Ok, so the last time I wrote C++ for a living, std::auto_ptr was all the std lib had available, and boost::shared_ptr was all the rage. I never really looked into the other smart pointer types boost provided. I understand that C++11 now provides some of the types boost came up with, but not all of them.

So does someone have a simple algorithm to determine when to use which smart pointer? Preferably including advice regarding dumb pointers (raw pointers like T*) and the rest of the boost smart pointers. (Something like this would be great).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

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

发布评论

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

评论(4

故事↓在人 2024-12-31 03:50:22

共享所有权:
采用的标准的 shared_ptrweak_ptr 与它们的 增强对应项。当您需要共享资源并且不知道哪个资源将是最后一个存活时,请使用它们。使用weak_ptr来观察共享资源而不影响其生命周期,不破坏循环。通常不会发生带有 shared_ptr 的循环 - 两个资源不能互相拥有。

请注意,Boost还提供shared_array,这可能是 shared_ptr 的合适替代方案。常量>

接下来,Boost 提供 intrusive_ptr,如果您的资源已经提供引用计数管理并且您希望将其采用 RAII 原则,那么这是一个轻量级解决方案。该标准未采用该标准。

独特的所有权:
Boost 还有一个 scoped_ptr,不可复制且无法为其指定删除程序。 std::unique_ptrboost::scoped_ptr 的增强版,并且应该是您当您需要智能指针时的默认选择。它允许您在其模板参数中指定删除器,并且是可移动的,这与 boost::scoped_ptr 不同。只要您不使用需要可复制类型的操作(显然),它也可以在 STL 容器中完全使用。

再次注意,Boost 有一个数组版本: scoped_array< /code>,该标准通过要求 std::unique_ptr 部分专业化来统一delete[] 指针而不是delete它(使用default_deleter)。 std::unique_ptr 还提供 operator[] 而不是 operator*operator->代码>.

请注意,std::auto_ptr 仍在标准中,但已被弃用。
§D.10 [depr.auto.ptr]

类模板 auto_ptr 已弃用。 [ 注意: 类模板 unique_ptr (20.7.1) 提供了更好的解决方案。 —尾注]

无所有权:
当您知道资源将比引用对象/作用域活得更久时,请使用哑指针(原始指针)或对资源的非拥有引用。当您需要可空性或可重置性时,首选引用并使用原始指针。

如果您想要对资源进行非拥有引用,但您不知道该资源是否会比引用它的对象寿命更长,请将资源打包在 shared_ptr 中并使用 weak_ptr< /code> - 您可以使用 lock 测试父 shared_ptr 是否处于活动状态,如果资源存在,它将返回非 null 的 shared_ptr仍然存在。如果想测试资源是否已失效,请使用expired。两者听起来很相似,但在并发执行时却有很大不同,因为 expired 仅保证该单个语句的返回值。像这样看似无辜的测试

if(!wptr.expired())
  something_assuming_the_resource_is_still_alive();

是潜在的竞争条件。

Shared ownership:
The shared_ptr and weak_ptr the standard adopted are pretty much the same as their Boost counterparts. Use them when you need to share a resource and don't know which one will be the last to be alive. Use weak_ptr to observe the shared resource without influencing its lifetime, not to break cycles. Cycles with shared_ptr shouldn't normally happen - two resources can't own each other.

Note that Boost additionally offers shared_array, which might be a suitable alternative to shared_ptr<std::vector<T> const>.

Next, Boost offers intrusive_ptr, which are a lightweight solution if your resource offers reference-counted management already and you want to adopt it to the RAII principle. This one was not adopted by the standard.

Unique ownership:
Boost also has a scoped_ptr, which is not copyable and for which you can not specify a deleter. std::unique_ptr is boost::scoped_ptr on steroids and should be your default choice when you need a smart pointer. It allows you to specify a deleter in its template arguments and is movable, unlike boost::scoped_ptr. It is also fully usable in STL containers as long as you don't use operations that need copyable types (obviously).

Note again, that Boost has an array version: scoped_array, which the standard unified by requiring std::unique_ptr<T[]> partial specialization that will delete[] the pointer instead of deleteing it (with the default_deleter). std::unique_ptr<T[]> also offers operator[] instead of operator* and operator->.

Note that std::auto_ptr is still in the standard, but it is deprecated.
§D.10 [depr.auto.ptr]

The class template auto_ptr is deprecated. [ Note: The class template unique_ptr (20.7.1) provides a better solution. —end note ]

No ownership:
Use dumb pointers (raw pointers) or references for non-owning references to resources and when you know that the resource will outlive the referencing object / scope. Prefer references and use raw pointers when you need either nullability or resettability.

If you want a non-owning reference to a resource, but you don't know if the resource will outlive the object that references it, pack the resource in a shared_ptr and use a weak_ptr - you can test if the parent shared_ptr is alive with lock, which will return a shared_ptr that is non-null if the resource still exists. If want to test whether the resource is dead, use expired. The two may sound similar, but are very different in the face of concurrent execution, as expired only guarantees its return value for that single statement. A seemingly innocent test like

if(!wptr.expired())
  something_assuming_the_resource_is_still_alive();

is a potential race condition.

小伙你站住 2024-12-31 03:50:22

决定使用什么智能指针是一个所有权问题。在资源管理方面,如果对象 A 控制着对象 B 的生命周期,则它拥有对象 B。例如,成员变量由其各自的对象拥有,因为成员变量的生命周期是绑定的到对象的生命周期。您可以根据对象的拥有方式来选择智能指针。

请注意,软件系统中的所有权与所有权是分开的,因为我们会在软件之外考虑它。例如,一个人可能“拥有”他们的家,但这并不一定意味着 Person 对象可以控制 House 对象的生命周期。将这些现实世界的概念与软件概念混为一谈,肯定会让自己陷入困境。


如果您拥有该对象的唯一所有权,请使用 std::unique_ptr

如果您共享该对象的所有权...
- 如果所有权没有循环,请使用 std::shared_ptr
- 如果存在循环,请定义一个“方向”,并在一个方向上使用 std::shared_ptr ,在另一个方向上使用 std::weak_ptr

如果该对象拥有您,但有可能没有所有者,请使用普通指针T*(例如父指针)。

如果该对象拥有您(或以其他方式保证存在),请使用引用T&


警告:请注意智能指针的成本。在内存或性能有限的环境中,仅使用普通指针和更手动的内存管理方案可能会很有帮助。

成本:

  • 如果您有自定义删除器(例如,您使用分配池),那么这将产生每个指针的开销,这可以通过手动删除轻松避免。
  • std::shared_ptr 具有复制时引用计数递增的开销,加上销毁时递减的开销,然后进行 0 计数检查并删除所持有的对象。根据实现的不同,这可能会使您的代码变得臃肿并导致性能问题。
  • 编译时间。与所有模板一样,智能指针会对编译时间产生负面影响。

示例:

struct BinaryTree
{
    Tree* m_parent;
    std::unique_ptr<BinaryTree> m_children[2]; // or use std::array...
};

二叉树不拥有其父级,但树的存在意味着其父级的存在(或根的 nullptr),因此使用普通指针。二叉树(具有值语义)对其子树拥有唯一的所有权,因此它们是 std::unique_ptr

struct ListNode
{
    std::shared_ptr<ListNode> m_next;
    std::weak_ptr<ListNode> m_prev;
};

在这里,列表节点拥有其下一个和上一个列表,因此我们定义一个方向,并使用 shared_ptr 表示 next 并使用 weak_ptr 表示 prev 来打破循环。

Deciding what smart pointer to use is a question of ownership. When it comes to resource management, object A owns object B if it is in control of the lifetime of object B. For example, member variables are owned by their respective objects because the lifetime of member variables is tied to the lifetime of the object. You choose smart pointers based on how the object is owned.

Note that ownership in a software system is separate from ownership as we would think of it outside of software. For example, a person might "own" their home, but that doesn't necessarily mean that a Person object has control over the lifetime of a House object. Conflating these real world concepts with software concepts is a sure-fire way to program yourself into a hole.


If you have sole ownership of the object, use std::unique_ptr<T>.

If you have shared ownership of the object...
- If there are no cycles in ownership, use std::shared_ptr<T>.
- If there are cycles, define a "direction" and use std::shared_ptr<T> in one direction and std::weak_ptr<T> in the other.

If the object owns you, but there is potential of having no owner, use normal pointers T* (e.g. parent pointers).

If the object owns you (or otherwise has guaranteed existence), use references T&.


Caveat: Be aware of the costs of smart pointers. In memory or performance limited environments, it could be beneficial to just use normal pointers with a more manual scheme for managing memory.

The costs:

  • If you have a custom deleter (e.g. you use allocation pools) then this will incur overhead per pointer that may be easily avoided by manual deletion.
  • std::shared_ptr has the overhead of a reference count increment on copy, plus a decrement on destruction followed by a 0-count check with deletion of the held object. Depending on the implementation, this can bloat your code and cause performance issues.
  • Compile time. As with all templates, smart pointers contribute negatively to compile times.

Examples:

struct BinaryTree
{
    Tree* m_parent;
    std::unique_ptr<BinaryTree> m_children[2]; // or use std::array...
};

A binary tree does not own its parent, but the existence of a tree implies the existence of its parent (or nullptr for root), so that uses a normal pointer. A binary tree (with value semantics) has sole ownership of its children, so those are std::unique_ptr.

struct ListNode
{
    std::shared_ptr<ListNode> m_next;
    std::weak_ptr<ListNode> m_prev;
};

Here, the list node owns its next and previous lists, so we define a direction and use shared_ptr for next and weak_ptr for prev to break the cycle.

嗫嚅 2024-12-31 03:50:22

始终使用 unique_ptr,除非需要引用计数,在这种情况下使用 shared_ptr(对于极少数情况,使用 weak_ptr ; 以防止引用循环)。几乎在所有情况下,可转让的独特所有权就可以了。

原始指针:仅当您需要协变返回时才有效,这种情况可能会发生非拥有指向。否则它们并不是很有用。

数组指针:unique_ptrT[] 有专门化,它会自动对结果调用 delete[],因此您可以安全地执行 unique_ptr; p(new int[42]); 例如。 shared_ptr 您仍然需要自定义删除器,但不需要专门的共享或唯一数组指针。当然,无论如何,这些东西通常最好用 std::vector 替换。不幸的是,shared_ptr不提供数组访问函数,因此您仍然需要手动调用get(),但是unique_ptr code> 提供 operator[] 而不是 operator*operator->。无论如何,你必须自我检查。这使得 shared_ptr 的用户友好性稍差,尽管可以说通用优势和无 Boost 依赖性使 unique_ptrshared_ptr 再次成为赢家。

作用域指针:通过 unique_ptr 变得无关紧要,就像 auto_ptr 一样。

真的没有什么更多的了。在没有移动语义的 C++03 中,这种情况非常复杂,但在 C++11 中,建议非常简单。

其他智能指针仍然有用途,例如 intrusive_ptrinterprocess_ptr。然而,它们非常小众,并且在一般情况下完全没有必要。

Use unique_ptr<T> all the time except when you need reference counting, in which case use shared_ptr<T> (and for very rare cases, weak_ptr<T> to prevent reference cycles). In almost every case, transferrable unique ownership is just fine.

Raw pointers: Good only if you need covariant returns, non-owning pointing which can happen. They're not terrifically useful otherwise.

Array pointers: unique_ptr has a specialization for T[] which automatically calls delete[] on the result, so you can safely do unique_ptr<int[]> p(new int[42]); for example. shared_ptr you'd still need a custom deleter, but you wouldn't need a specialized shared or unique array pointer. Of course, such things are usually best replaced by std::vector anyway. Unfortunately shared_ptr does not provide an array access function, so you'd still have to manually call get(), but unique_ptr<T[]> provides operator[] instead of operator* and operator->. In any case, you have to bounds check yourself. This makes shared_ptr slightly less user-friendly, although arguably the generic advantage and no Boost dependency makes unique_ptr and shared_ptr the winners again.

Scoped pointers: Made irrelevant by unique_ptr, just like auto_ptr.

There's really nothing more to it. In C++03 without move semantics this situation was very complicated, but in C++11 the advice is very simple.

There are still uses for other smart pointers, like intrusive_ptr or interprocess_ptr. However, they're very niche and completely unnecessary in the general case.

作死小能手 2024-12-31 03:50:22

何时使用 unique_ptr 的情况:

  • 工厂方法
  • 作为指针的成员(包括 pimpl)
  • 将指针存储在 stl 容器中(以避免移动)
  • 使用大型本地动态对象

何时使用 shared_ptr< /code>:

  • 跨线程共享对象
  • 一般

情况下共享对象 何时使用 weak_ptr 的情况:

  • 一般参考的大地图(例如所有打开的套接字的地图)

充当 编辑并添加更多

Cases of when to use unique_ptr:

  • Factory methods
  • Members that are pointers (pimpl included)
  • Storing pointers in stl containters (to avoid moves)
  • Use of large local dynamic objects

Cases of when to use shared_ptr:

  • Sharing objects across threads
  • Sharing objects in general

Cases of when to use weak_ptr:

  • Large map that acts as a general reference (ex. a map of all open sockets)

Feel free to edit and add more

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