关于 shared_ptr 引用计数块

Regarding shared_ptr reference count block

我有 2 个关于 std::shared_ptr 控制块的问题:

(1) 关于尺码: 我如何以编程方式找到std::shared_ptr的控制块的确切大小?

(2) 关于逻辑: 此外,boost::shared_ptr 提到它们在控制块中的更改方面是完全无锁的。(从 Boost 版本 1.33.0 开始,shared_ptr 在最常见的平台。) 我不认为 std::shared_ptr 遵循相同的规则 - 这是否计划用于任何未来的 C++ 版本?这是否也意味着 boost::shared_ptr 对于多线程情况是更好的主意?

控制块没有暴露。在我读过的实现中,它的大小是动态的,可以连续存储删除器(and/or,在 make shared 的情况下,对象本身)。

一般来说,它至少包含 3 个指针大小的字段 - weak、strong count 和 deleter invoker。

至少有一种实现依赖于 RTTI;其他人没有。

计数操作在我阅读的实现中使用了原子操作;请注意,C++ 不要求原子操作全部无锁(我相信没有指针大小无锁操作的平台可以是符合标准的 C++ 平台)。

它们的状态彼此一致,也与它们自身一致,但没有尝试使它们与对象状态一致。这就是为什么在某些平台上使用原始共享 ptrs 作为写时复制 pImpls 可能容易出错的原因。

(1) Regarding size: How can I programatically find the exact size of the control block for a std::shared_ptr?

没有办法。它不能直接访问。

(2) Regarding logic: Additionally, boost::shared_ptr mentions that they are completely lock-free with respect to changes in the control block.(Starting with Boost release 1.33.0, shared_ptr uses a lock-free implementation on most common platforms.) I don't think std::shared_ptr follows the same - is this planned for any future C++ version? Doesn't this also mean that boost::shared_ptr is a better idea for multithreaded cases?

绝对不是。无锁实现并不总是比使用锁的实现更好。有一个额外的约束,充其量不会使实现变得更糟,但它不可能使实现更好。

考虑两个同样有能力的程序员,每个人都尽最大努力实现 shared_ptr。必须产生一种无锁实现。另一个人可以完全自由地使用他们的最佳判断。在所有其他条件相同的情况下,必须产生无锁实现的人根本不可能产生更好的实现。充其量,无锁实现是最好的,他们都会产生一个。更糟糕的是,在这个平台上,无锁实现有很大的缺点,一个实现者必须使用一个。呸

(1) 当然最好检查实现,但是您仍然可以从您的程序中进行一些检查。

控制块是动态分配的,因此要确定其大小,您可以重载新运算符。

然后您还可以检查 std::make_shared 是否为您提供了一些控制块大小的优化。 在正确的实施中,我希望这将进行两次分配(objectA 和控制块):

std::shared_ptr<A> i(new A());

但是这只会进行一次分配(然后使用新放置初始化 objectA):

auto a = std::make_shared<A>();

考虑以下示例:

#include <iostream>
#include <memory>

void * operator new(size_t size) 
{ 
    std::cout << "Requested allocation: " << size << std::endl; 
    void * p = malloc(size); 
    return p; 
} 

class A {};

class B
{
    int a[8];
};

int main()
{
  std::cout << "Sizeof int: " << sizeof(int) << ", A(empty): " << sizeof(A) << ", B(8 ints): " << sizeof(B) << std::endl;
  {
      std::cout << "Just new:" << std::endl;
      std::cout << "- int:" << std::endl;
      std::shared_ptr<int> i(new int());
      std::cout << "- A(empty):" << std::endl;
      std::shared_ptr<A> a(new A());
      std::cout << "- B(8 ints):" << std::endl;
      std::shared_ptr<B> b(new B());
  }
  {
      std::cout << "Make shared:" << std::endl;
      std::cout << "- int:" << std::endl;
      auto i = std::make_shared<int>();
      std::cout << "- A(empty):" << std::endl;
      auto a = std::make_shared<A>();
      std::cout << "- B(8 ints):" << std::endl;
      auto b = std::make_shared<B>();
  }
}

我收到的输出(当然是特定于硬件架构和编译器的):

Sizeof int: 4, A(empty): 1, B(8 ints): 32
Just new:
- int:
Requested allocation: 4
Requested allocation: 24

第一个分配给 int - 4 个字节,下一个分配给控制块 - 24 个字节。

- A(empty):
Requested allocation: 1
Requested allocation: 24
- B(8 ints):
Requested allocation: 32
Requested allocation: 24

看起来控制块(很可能)是 24 字节。

这里是为什么要使用 make_shared:

Make shared:
- int:
Requested allocation: 24

只有一次分配,int + control block = 24 bytes,比之前少

- A(empty):
Requested allocation: 24
- B(8 ints):
Requested allocation: 48

这里应该是 56 (32+24),但看起来实现已经优化了。如果您使用 make_shared - 控制块中不需要指向实际对象的指针,它的大小只有 16 个字节。

检查控制块大小的其他可能性是:

std::cout<< sizeof(std::enable_shared_from_this<int>);

就我而言:

16

所以我会说在我的例子中控制块的大小是 16-24 字节,这取决于它是如何创建的。