我在很多地方读到使用 make_shared<T>
时创建 shared_ptr<T>
, 它的控制 block 包含一个足够大的存储 block 来容纳 T
,然后在存储中构造对象,并放置新的位置。像这样的:
template<typename T>
struct shared_ptr_control_block {
std::atomic<long> count;
std::atomic<long> weak_count;
std::aligned_storage_t<sizeof (T), alignof (T)> storage;
};
但我有点困惑,为什么我们不能只使用类型为 T
的成员变量反而?为什么要创建原始存储然后使用新放置?它不能与 T
类型的普通对象一步结合吗? ?
最佳答案
这是为了允许生命周期管理。
在weak_count
为零之前,控制 block 不会被销毁。一旦 count
达到零,storage
对象就会被销毁。也就是说,当计数为零时,需要直接调用storage
的析构函数,而不在控制 block 的析构函数中。
为了防止控制 block 的析构函数调用storage
的析构函数,storage
的实际类型不能是T
。
如果我们只有强引用计数,那么 T
就可以了(而且更简单)。
实际上,实现比这要复杂一些。请记住,可以通过使用 new
分配 T
,然后从中构造 shared_ptr
来构造 shared_ptr。因此 actual 控制 block 看起来更像:
template<typename T>
struct shared_ptr_control_block {
std::atomic<long> count;
std::atomic<long> weak_count;
T* ptr;
};
而make_shared
分配的是:
template<typename T>
struct both {
shared_ptr_control_block cb;
std::aligned_storage_t<sizeof (T), alignof (T)> storage;
};
而cb.p
设置为storage
的地址。在 make_shared
中分配 both
结构意味着我们得到一个内存分配,而不是两个(而且内存分配很昂贵)。
注意:我已经简化了:必须有一种方法让 shared_ptr 析构函数知道控制 block 是否是 both
的一部分(在这种情况下,在完成之前无法释放内存),或者不是(在这种情况下可以更早地释放它)。这可能是一个简单的 bool 标志(在这种情况下控制 block 更大),或者通过在指针中使用一些备用位(这不是可移植的 - 但标准库实现不必是可移植的)。为了避免在 make_shared
情况下存储指针根本,实现甚至可以更加复杂。
关于c++ - 为什么 shared_ptr 使用placement new,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41716083/