shared_ptr vs CComPtr

2019-02-18 03:26发布

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.

  • 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...

  • 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?

  • 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?

1条回答
Bombasti
2楼-- · 2019-02-18 03:44

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

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.

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

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).

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.

Is there an alternative with shared/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

See make_shared, above.

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).

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.

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.

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

查看更多
登录 后发表回答