C++11 参考计数智能指针设计

4
我正在阅读以下内容:
http://www.informit.com/articles/article.aspx?p=31529&seqNum=5
作者在文中解释了三种智能指针设计(请参见文章末尾的图片)。
我相信当前的GCC、CLang和可能的Visual C++使用的是带控制块的智能指针。
我可以想象为什么不使用“内部引用计数”,但第二种实现——“带指向指针块的智能指针”有什么问题吗?虽然需要两个指针解引用,但智能指针对象的大小将只有一半。
“带控制块的智能指针”

smart pointer with control block

智能指针与指向指针块的指针。

smart pointer with pointer to pointer block

智能指针与侵入式引用计数。

enter image description here


为什么不读代码,看看实现到底做了什么,而不是只凭想象呢? - Kerrek SB
4
使用make_shared时,许多实现会像上面展示的那样实现内部引用计数的内存布局。这种方法的好处是只使用单个分配,但缺点是仅有一个weak_ptr指向对象时,即使没有任何smart_ptr指向该对象,整个内存也将被保留。例如参考Scott Meyers的《More Effective C++》。 - Peter G.
2
std::unique_ptr 不执行任何额外的堆分配,而 make_shared 则会。因此,内存布局肯定会有所不同。如果您指的是 auto up = std::shared_ptr(ptr); - 那么是的,这是一种常见的实现技术,使 make_shared 将对象本身和控制块的内存合并为单个堆分配,而 shared_ptr 的构造函数显然无法做到这一点(对象的分配已经发生)。 - Igor Tandetnik
1
双重解引用和胖指针都很耗费资源。在某些情况下,每种技术都可能更好,但在其他情况下则不然。实现必须选择一种技术,因此我认为选择是在仔细考虑所有事情之后进行的,并且所选的变体将为大多数人提供最佳解决方案。但实际上,谁知道呢。 - n. m.
2
“带控制块的智能指针”使用略多的内存,但允许更快地对智能指针进行解引用。使用指向指针块的指针,您需要在获取真实对象的地址之前从SPimpl对象中额外加载pObj_的内存。 - Jan Lucas
显示剩余3条评论
1个回答

4
重要的一个原因是性能问题,如果对象直接存储在 shared_ptr 对象中,则 shared_ptr::get() 不需要解引用指针来查找对象地址。
除了性能外,指向指针块的智能指针实现将不支持您可以使用 shared_ptr 做的所有事情,例如:
std::shared_ptr<int> pi(new int(0));
std::shared_ptr<void> pv = pi;
std::shared_ptr<int> pi2 = static_pointer_cast<int>(pv);

struct A {
  int i;
};
std::shared_ptr<A> pa(new A);
std::shared_ptr<int> pai(pa, pa->i);

struct B { virtual ~B() = default; };
struct C : B { };
std::shared_ptr<B> pb(new C);
std::shared_ptr<C> pc = std::dynamic_pointer_cast<C>(pb);

在这些例子中,pvpaipb存储的是一个指向与控制块所拥有的指针类型不同的指针,因此必须在shared_ptr本身中存储第二个指针(可能是不同类型的指针)。
对于pvpb,可以通过将存储在控制块中的指针转换为需要返回的类型来使其工作。虽然有一些使用多重继承的示例无法正确工作,但在某些情况下,这种方法是可行的。
但是对于使用别名构造函数的pai示例,没有办法使其工作而不存储与控制块中的指针分离的指针,因为这两个指针是完全不相关的类型,无法在它们之间进行转换。
你在评论中说:

I see and in case of make_shared, second pointer points to the address internal to the allocated block. (I actually tried this already and it seems that way)

是的,这是正确的。仍然存在第二个指针,但两个指针都指向同一块内存。这样做的好处是只需要进行一次内存分配,而不是为对象和控制块分别进行两次分配。此外,对象和控制块在内存中相邻,因此更有可能共享一个高速缓存行。如果CPU已经将引用计数存储在其缓存中,则它可能也已经将对象存储在其缓存中,因此访问它们两个的速度更快,并且意味着还可以使用另一个高速缓存行来用于其他数据。

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