我在许多地方都读过,当使用make_shared<T>
创建shared_ptr<T>
时,其控制块包含一个足以容纳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
类型的普通对象一起组合使用吗?
答案 0 :(得分:18)
允许终身管理。
在weak_count
为零之前,控制块不会被销毁。只要storage
达到零,count
对象就会被销毁。这意味着当计数达到零时你需要直接调用storage
的析构函数,而控制块的析构函数中不是。
为防止控制块的析构函数调用storage
的析构函数,storage
的实际类型不能为T
。
如果我们只有强引用计数,那么T
就可以了(并且更简单)。
实际上,实现比这复杂一点。请记住,可以通过使用T
分配new
,然后从中构建shared_ptr
来构建shared_ptr。因此,实际控制块看起来更像:
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
的地址。在both
中分配make_shared
结构意味着我们获得单个内存分配,而不是两个(并且内存分配很昂贵)。
注意:我已经简化了:shared_ptr析构函数必须有一种方法可以知道控制块是否是both
的一部分(在这种情况下,内存在完成之前无法释放),或者不是(在哪种情况可以提前释放)。这可能是一个简单的bool标志(在这种情况下控制块更大),或者通过在指针中使用一些备用位(这是不可移植的 - 但标准库实现不必是可移植的)。实现甚至可以更多复杂,以避免在make_shared
情况下将指针存储在所有中。
答案 1 :(得分:7)
由于弱指针可能比存储对象寿命长,控制块的生命周期可能必须超过存储对象的生命周期。如果托管对象是一个成员变量,它只能在控制块被销毁时被销毁(或者析构函数将被调用两次)。
即使在对象本身被破坏之后存储仍保持分配的事实在内存约束系统中实际上是make_shared
的缺点(尽管我不知道这是否是在实践中遇到的事情)。