关于 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 字节,这取决于它是如何创建的。
我有 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 字节,这取决于它是如何创建的。