shared_ptr和CComPtr的区别

7

我对通过COM进行引用计数的概念有些熟悉,但对shared_ptr还比较新。CComPtr具有几个很好的属性,而这些属性在shared_ptr中找不到,我想知道有哪些模式可以防止shared_ptr被误用。

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

  • 一个小角落的情况:过去我使用AddRef/Release做过的事情:我想要一个IFoo的“弱引用”容器(例如从URL到IConnection的映射)。使用weak_ptr,我可以做到这一点,但我的集合不会自动清理,其中将包含过期指针。使用Release,我可以实现自己的弱指针(需要一些工作),它实际上可以清理集合。在shared/weak_ptr中是否有替代方案?

  • 直觉上,创建对象时进行两个内存分配(一个用于引用计数,一个用于对象)相对于IUnknown世界中只进行一次内存分配会有性能损失。当访问对象时也存在局部性惩罚(假设AddRef经常后跟读取对象的内容,这似乎很可能)。这两种方法的成本是否进行了比较?


1
如果使用make_shared,则不会产生两次分配。否则,请发布您的实际用例,因为在空气中进行争论是很困难的。 shared_ptr非常好,但并不是所有情况的唯一解决方案。 - Kerrek SB
@KerrekSB:请注意,make_shared并不需要这样做。所有实现都会这样做,但这并不是严格的要求。 - Nicol Bolas
1
CComPtr和普通指针的最大区别是,它不需要存储引用计数。引用计数被保存在对象中。同时,它也无需担心线程安全,这对性能来说非常重要。 - Hans Passant
1个回答

7
如果shared_ptr这么不安全,为什么标准提供了一个以T*为参数的构造函数呢?
因为它是唯一一种不具侵入性的使用shared_ptr的方式。您可以在任何对象上使用shared_ptr,甚至可以通过使用删除器对象在C接口对象上使用它们,比如cairo_t*等。这样,我就永远不必再释放任何东西了。
但是,如果使用CComPtr,则只适用于IUnknown-样式的对象。
此外,还有std::make_shared,它直接从对象类型和构造函数参数创建shared_ptr。因此,您甚至不需要看到指针(通常会将对象及其引用计数分配在一次分配中,而不是两次)。
使用shared_ptr的正确做法非常简单:始终使用make_sharedalloc_shared。如果无法使用它们,则适当的做法是仅与new一起使用直接裸指针构造函数:shared_ptr<T> pVal{new T{...}};(或创建指针的适当函数)。永远不要在不知道起源的指针上使用它。
是否有使用shared/weak_ptr的替代方法?
没有,但如果您愿意,可以使用工具制定替代方案。除了显而易见的方法(定期遍历集合并删除死weak_ptr)外,您还可以将删除器与shared_ptr相关联,以便在删除指针时调用任何清理函数以删除这些weak_ptr
直觉上,创建对象需要进行两次内存分配会有性能损失。
请参见上面的make_shared
在访问对象时,也存在局部性惩罚(假设AddRef经常后跟读取对象内容,这似乎很可能)。
您不必复制shared_ptr来访问其内容,也不必增加引用计数就可以这样做。
现在,让我们谈谈一些CComPtr无法做到的事情。它具有侵入性。它不能与任意分配器或删除器一起使用(当然对于侵入性不是很重要)。它不能进行指针别名,其中您将shared_ptr与对象成员关联,但实际的引用计数是针对其所属对象的。这是一个非常有用的功能。

哦,没错,它不是跨平台的。它没有绑定到COM、IUnknown和所有那些开销。


“始终使用make_shared或alloc_shared”:如果我永远不使用接受T*的构造函数(而始终使用make_shared),那么标准为什么提供它呢? 我知道使用make_shared可以消除性能损失,这很酷。 您是否发现自己创建的类既可以使用make_shared实例化,也可以通过其他方式(例如unique_ptr)实例化?如何进行限制性插入? AddRef / Release可以进行别名处理(因为您实现了API,所以可以重定向调用)。侵入式引用计数的概念是跨平台的(我的意思是,标准可以采用“类似CComPtr”的类) - user1204233
@user1204233:你没有看到我接下来说的话吗,“如果你不能使用它们……”?有些情况下是不可能使用它们的。应该在可能的情况下使用它们,在不可能的情况下使用其他东西。至于侵入式引用计数的限制,向我展示如何在不修改类的情况下使用cairo_t任何预先存在的类。必须修改代码才能使用智能指针是一种限制。并不是每个人都拥有他们使用的每一行代码。您可以将shared_ptrvectorstring、iostreams以及任何未来的类一起使用,无论任何人在任何地方编写。 - Nicol Bolas
@user1204233: "我的意思是,标准可以采用类似于“CComPtr”的类"。那不是你所的问题。你具体问的是CComPtr,所以我也具体回答了你关于CComPtr的问题。如果你修改你的问题,更具体地涉及侵入式引用计数,那么我会修改我的答案。并且可能会将其移动到Programmers.se。 - Nicol Bolas
2
保持冷静,我并不是有意冒犯任何人... 什么情况下不能使用make_shared? 您可以将IString放在字符串周围,以添加引用计数,现在您有一个新类,可以添加引用/释放。您甚至可以将其模板化为with_ref<string>,这将是一个公开AddRef / Release的类。想起来了,这正是相同的模式...除了有一种方法可以创建围绕现有字符串的新shared_ptr(您不应该这样做),但没有办法将字符串转换为with_ref<string>(除了向下转换,您不应该这样做:)) - user1204233
1
@user1204233: "在什么情况下不能使用make_shared?" 这是一个新问题,就像"IString"的概念一样。也许你应该问这个问题;听起来是一个好问题。Stack Overflow不是一个论坛;评论区不适合跟进问题。如果一个答案引发了你的新问题,请使用“提问”按钮来提出它们。 - Nicol Bolas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接