shared_ptr本身是安全无锁的,但shared_ptr对象的读写并不是,因为shared_ptr拥有两个数据成员,读写操作不能原子化。
参考文献:C++ shared_ptr四宗罪(不得不转),shared_ptr共享型智能指针详解
shared_ptr数据结构
shared_ptr 是引用计数型(reference counting)智能指针,几乎所有的实现都采用在堆(heap)上放个计数值(count)的办法(除此之外理论上还有用循环链表的办法,不过没有实例)。具体来说,shared_ptr
- 控制块中的数据有指向Foo的指针,为何?
shared_ptrshared_ptr.ptr
和ref_count.ptr
可以是不同的类型(存在隐式转换)
1. 无需虚析构;假设 Bar 是 Foo 的基类,但是 Bar 和 Foo 都没有虚析构。
shared_ptr<Foo> sp1(new Foo); // ref_count.ptr 的类型是 Foo*
shared_ptr<Bar> sp2 = sp1; // 可以赋值,自动向上转型(up-cast)
sp1.reset(); // 这时 Foo 对象的引用计数降为 1
此后 sp2
仍然能安全地管理 Foo 对象的生命期,并安全完整地释放 Foo,因为其 ref_count 记住了 Foo 的实际类型。
2.** shared_ptr<void>**
可以指向并安全地管理(析构或防止析构)任何对象;muduo::net::Channel class 的 tie() 函数就使用了这一特性,防止对象过早析构,见书 7.15.3 节。
shared_ptr<Foo> sp1(new Foo); // ref_count.ptr 的类型是 Foo*
shared_ptr<void> sp2 = sp1; // 可以赋值,Foo* 向 void* 自动转型
sp1.reset(); // 这时 Foo 对象的引用计数降为 1
此后 sp2 仍然能安全地管理 Foo 对象的生命期,并安全完整地释放 Foo,不会出现 delete void 的情况,因为 delete 的是 ref_count.ptr,不是 sp2.ptr。
*3. 多继承。假设 Bar 是 Foo 的多个基类之一,那么:
shared_ptr<Foo> sp1(new Foo);
shared_ptr<Bar> sp2 = sp1; // 这时 sp1.ptr 和 sp2.ptr 可能指向不同的地址,因为 Bar subobject 在 Foo object 中的 offset 可能不为0。
sp1.reset(); // 此时 Foo 对象的引用计数降为 1
但是 sp2 仍然能安全地管理 Foo 对象的生命期,并安全完整地释放 Foo,因为 delete 的不是 Bar,而是原来的 Foo。换句话说,sp2.ptr 和 ref_count.ptr 可能具有不同的值(当然它们的类型也不同)。
多线程无保护写造成race condition
shared_ptr<Foo> g(new Foo); // 线程之间共享的 shared_ptr
shared_ptr<Foo> x; // 线程 A 的局部变量
shared_ptr<Foo> n(new Foo); // 线程 B 的局部变量
//线程A执行
x=g;
//线程B执行
g=n;
//此时会造成竞争
- 初始状态
x=g
,仅仅先完成了指针的指向,控制块还未完成
g=n
,2个步骤均先完成了,此时Foo1对象析构
多线程无保护地读写 g,造成了“x 是空悬指针”的后果。这正是多线程读写同一个 shared_ptr 必须加锁的原因。
为什么要尽量用make_shared——节省1次内存分配
在调用share_ptr