shared_ptr 就像一个"大院子"住户,不仅要管理自己的房子(对象),还得维护一个"院子公告栏"(控制块)。
而 Arc 则像个"精装修"业主,所有信息都紧凑地塞在一起。
从内存布局上来看:
shared_ptr 的"大宅院"布局:
template <typename T>
class shared_ptr {
T* ptr; // 8字节,指向对象
control_block* cb; // 8字节,指向控制块
};
控制块还得单独开辟一块地方用来记录:引用计数、弱引用计数、删除器、分配器
Arc 的"精装修"布局:
pub struct Arc<T> {
ptr: NonNull<ArcInner<T>>, // 8字节,指向内部结构
}
struct ArcInner<T> {
strong: atomic<usize>, // 引用计数
data: T, // 实际数据
}
为什么会有这样的差异?这就要从两门语言的设计理念说起了:
C++ 的理念是灵活性至上,shared_ptr 支持自定义删除器和分配器,可以从裸指针构造并且支持多态 这些灵活性都是要付出代价的 —— 那就是额外的内存开销。
而Rust 的理念是安全性至上,Arc 的设计更加克制,专注于线程安全的共享所有权,没有弱引用计数(有另外的 Weak 类型),数据和引用计数紧密相连。
从性能角度来看,shared_ptr 需要两次解引用才能访问数据:一次到控制块,一次到实际对象。Arc 只需要一次解引用就能访问数据和引用计数
不过也别觉得 shared_ptr 就是"败犬":它的灵活性在某些场景下是无可替代的,而且现在的编译器的优化已经能够很好地处理这种双重指针结构。在不需要自定义删除器时,很多实现会把控制块和对象分配在连续内存中。
内存布局不是单纯的技术选择,而是语言设计理念的折射。C++ 选择了灵活性,Rust 选择了安全性,都是各自生态系统中最好的选择。
如果就是要问:那我该用哪个?
答案很简单:用 C++ 就用 shared_ptr,用 Rust 就用 Arc。不要试图让语言去适应你,而是要适应语言的思维方式。
如果觉得文章有帮助,欢迎点赞关注,我是旷野,探索无尽技术!