shared_ptr 与 CComPtr

2022-01-14 00:00:00 com c++ shared-ptr

我有点习惯于通过 COM 引用计数的概念,而且我对 shared_ptr 有点陌生.CComPtr 有几个不错的属性,我在 shared_ptr 中找不到,我想知道防止误用 shared_ptr 的模式是什么.

I'm somewhat used to the concept of refcounting through COM and I'm somewhat new to shared_ptr. There are several nice properties with CComPtr that I don't find in shared_ptr, and I'm wondering what are the pattern that prevent missuse of shared_ptr.

  • AddRef/Release 模式保证每个对象只有一个引用计数(引用计数存储在对象本身上),所以当您有一个随机指针围绕它创建一个 CComPtr 时,它是安全的.另一方面,shared_ptr 有一个单独的 refcount 指针,因此在对象上创建新的 shared_ptr 是不安全的(如果这样做不安全,为什么标准提供了一个在 shared_ptr 上接受 T* 的构造函数?).这似乎是一个很大的限制,我不明白如何使用 shared_ptrs...

  • The AddRef/Release pattern guarantees there is only one refcount per object (the refcount is stored on the object itself), so it's safe, when you have a random pointer to create a CComPtr around it. On the other hand, shared_ptr has a separate refcount pointer, so it's unsafe to create a new shared_ptr on an object (why does the standard provide a constructor that takes a T* on shared_ptr if it's so unsafe to do?). That seems such a big limitation that I don't understand how one can use shared_ptrs...

有点极端的情况:我过去用 AddRef/Release 做过的事情:我想要一个 IFoos 的弱引用"容器(例如从 URL 到 IConnection 的映射或其他东西).使用weak_ptr,我可以做到这一点,但我的收藏不会自行清理",我会在其中包含过期的指针.使用 Release,我可以实现我自己的弱指针(一些工作),它实际上清理了集合.shared/weak_ptr 有替代方案吗?

A bit corner case: something that I've done in the past with AddRef/Release: I want a container of "weak references" to IFoos (for example a map from URL to IConnection or something). With weak_ptr, I can do that but my collection won't "clean itself up", I'll have expired pointers in it. With Release, I can implement my own weak pointer (a bit of work) that actually cleans up the collection. Is there an alternative with shared/weak_ptr?

直观地说,与只执行一次的 IUnknown 世界相比,执行两次内存分配来创建一个对象(一个用于引用计数,一个用于对象)会降低性能.访问对象时也存在局部性损失(假设 AddRef 之后经常读取对象的内容,这似乎很可能).是否比较了两种方法的成本?

Intuitively, there is a performance penalty in doing two memory allocations to create an object (one for the refcount, one for the object) compared to the IUnknown world where you do only one. There is also a locality penalty when accessing the object (assuming that an AddRef is frequently followed by reading the content of the object, which seems likely). Has the cost of both approaches been compared?

推荐答案

如果这样做不安全,为什么标准提供了一个在 shared_ptr 上接受 T* 的构造函数?

why does the standard provide a constructor that takes a T* on shared_ptr if it's so unsafe to do?

因为这是拥有 shared_ptr 而不会侵入的唯一方法.您可以在 anything 上使用 shared_ptr.我什至通过使用删除器对象在 C 接口的对象上使用它们.cairo_t* 之类的东西.这样一来,我就再也不用释放任何东西了.

Because it's the only way to have shared_ptrs without being intrusive. You can use a shared_ptr on anything. I've even used them on objects from C interfaces, via the use of a deleter object. Things like a cairo_t* and so forth. That way, I never have to free anything ever again.

CComPtr 无法做到这一点;它仅适用于 IUnknown 样式的对象.

You can't do that with CComPtr; it only works for IUnknown-style objects.

另外,还有std::make_shared,它直接从对象类型和构造函数的参数创建一个shared_ptr.所以你甚至看不到指针(它通常在一次分配中分配对象及其引用计数而不是两次).

Also, there is std::make_shared, which creates a shared_ptr directly from an object type and the argument to the constructor. So you never even see the pointer (and it usually allocates the object and its ref-count in one allocation instead of two).

shared_ptr 的正确 C++ 习惯用法非常简单:始终使用 make_sharedalloc_shared.如果您不能使用它们,那么正确的习惯用法是仅将直接裸指针构造函数与new一起使用:shared_ptr<T>pVal{new T{...}};(或创建指针的适当函数).永远不要在不知道其来源的指针上使用它.

The proper C++ idiom with shared_ptr is very simple: always use make_shared or alloc_shared. If you can't use them, then the proper idiom is to only use the direct naked pointer constructor in tandem with new: shared_ptr<T> pVal{new T{...}}; (or the appropriate function that creates the pointer). Never use it on pointers that you don't know the origin of.

shared/weak_ptr 有替代方案吗?

Is there an alternative with shared/weak_ptr?

不,但如果您愿意,可以使用工具制作一个.除了显而易见的方法(定期运行您的集合并删除死 weak_ptrs)之外,您还可以将删除器与将(除了删除指针)调用的 shared_ptr 相关联删除那些 weak_ptr 的任何清理函数.

No, but there are tools to make one if you so desire. Besides the obvious method (run through your collection periodically and remove dead weak_ptrs), you can associate a deleter with the shared_ptr that will (in addition to deleting the pointer) call whatever cleanup function to remove those weak_ptrs.

直观地说,在创建对象时进行两次内存分配会降低性能

Intuitively, there is a performance penalty in doing two memory allocations to create an object

参见上面的 make_shared.

在访问对象时还有一个局部性惩罚(假设 AddRef 之后经常读取对象的内容,这似乎很可能).

There is also a locality penalty when accessing the object (assuming that an AddRef is frequently followed by reading the content of the object, which seems likely).

您无需复制 shared_ptr 即可与其内容对话,也无需增加引用计数.

You don't have to copy the shared_ptr to talk to its contents, nor do you have to bump the reference count to do so.

现在,让我们谈谈CComPtr 不能 做的一些事情.这是侵入性的.它不能与任意分配器或删除器一起使用(当它具有侵入性时显然不那么重要).它不能做指针别名,你有一个对象成员的 shared_ptr ,但实际的引用计数是它所属的对象.这是一件非常有用的事情.

Now, let's talk about some of the things CComPtr can't do. It's intrusive. It can't be used with arbitrary allocators or deleters (obviously not as important when it's intrusive). It can't do pointer aliasing, where you have a shared_ptr to a member of an object, but the actual reference count is for the object it is a member of. That's a very useful thing to be able to do.

哦,是的,它不是跨平台的.它不受 COM、IUnknown 和所有 开销的约束.

Oh yeah, it's not cross-platform. It's not bound to COM, IUnknown, and all of that overhead.

相关文章