shared_ptr
1. 简介
前面一篇文章 已经介绍过智能指针
unique_ptr , 本节介绍
shared_ptr .
std::shared_ptr 通过一个指针,
持有一个共享对象的所有权. C++ 允许多个
shared_ptr 指向同一个对象.
当出现如下情形时, shared_ptr
指向的对象会被销毁且其占用的内存空间会被回收:
最后一个占有对象的指针 shared_ptr
被销毁;
最后一个占有对象的指针 shared_ptr
通过重载运算 operator=
赋值给其他指针, 或者调用函数
reset()
时.
上面的对象销毁可以使用删除器表达式, 或者是在构造期间提供给
shared_ptr 的一个自定义删除器.
shared_ptr
能够在存储指向一个对象的指针时, 共享另一个对象的所有权.
这一特性可以用来在占有其所属对象时, 指向成员对象. 所存储的指针可以被函数
get()
、解引用以及比较运算符访问. 当引用计数为 0 时,
被管理的指针会被传递给删除器.
与 unique_ptr 类似,
shared_ptr 也可以不占有任何对象,
这种情况下即为 empty . 若一个空
shared_ptr 是通过别名构造器创建的,
那么它就可能拥有非空的存储指针.
在不附加同步的情况下, 多个线程能在
shared_ptr 的不同实例上调用所有的成员函数,
包括拷贝构造函数和拷贝赋值函数, 即使这些实例是副本,
且共享同一个对象的所有权. 如果多个线程的执行, 访问同一个
shared_ptr 的实例而不进行同步,
并且任一线程调用 shared_ptr 的非
const
成员函数, 那么就会出现数据竞争;
shared_ptr
中原子函数的重载可以避免数据竞争的发生.
另外一个 shared_ptr
指针要想共享一个对象的所有权,
只能通过拷贝构造或者拷贝赋值的方式将其值赋给这个
shared_ptr 指针. 换句话说, 用另一个
shared_ptr 所占有的原始指针创建新的
shared_ptr 将会导致未定义行为.
当然, std::shared_ptr 允许不完整类型 T.
但是, 来自原始指针的构造器
template<class Y> shared_ptr(Y*)
与 成员函数
template<class Y> void reset(Y*)
只能被指向完整类型的指针调用, 应当注意的是,
std::shared_ptr
可以通过一个原始指针构造成不完整类型.
std::shared_ptr 中的 T
可以是函数类型: 也就是说, 这种情况下, T 是一个函数指针,
而不是简单的对象指针.
这种情况可以用来保有一个动态库或者作为一个插件进行装载,
只要任意函数被引用了:
1 2 3 4 5 6 void del (void (*)()) {}void fun () {}int main () { std::shared_ptr<void () > ee (fun, del) ; (*ee)(); }
在如下典型的实现方式中, std::shared_ptr
只持有两个指针:
所谓的控制块是一种动态分配的对象:
当通过 std::make_shared 或者
std::allocate_shared 方式创建
shared_ptr 对象时,
控制块和被管理对象二者的内存空间都是一次分配的.
被管理对象是在控制块的数据成员中原位构造.
如果 shared_ptr
是通过某个构造函数进行构造的,
那被管理的对象和控制块必须分别进行内存空间的分配.这种情况下,
控制块存有一个指向被管理对象的指针.
shared_ptr 所持有的指针可以通过
get()
直接返回,
而由控制块管理的指针或者对象则是引用计数为0时被释放的那个. 因此,
二者并不一定等价.
shared_ptr
的析构函数将控制块的引用计数减一. 如果引用计数减为0,
控制块将会调用被管理对象的析构函数. 但控制块直到
std::weak_ptr 的引用计数为零时,
才会被释放自身.
在现存实现方式中, 如果有一个共享指针指向同一个控制块,
那弱指针数会自增一.
同时, 为了满足线程安全的要求, 引用计数会等价地使用
std::memory_order_relaxed 中的
std::atomic::fetch_add 自增; 如果自减,
则要求严格的顺序, 安全地销毁控制块.
2. 示例
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 #include <iostream> #include <memory> #include <thread> #include <chrono> #include <mutex> struct Base { Base () { std::cout << " Base::Base()\n" ; } ~Base () { std::cout << " Base::~Base()\n" ; } };struct Derived : public Base { Derived () { std::cout << " Derived::Derived()\n" << '\n' ; } ~Derived () { std::cout << "\n" ; std::cout << " Derived::~Derived()\n" ; } };void thr (std::shared_ptr<Base> p) { std::this_thread::sleep_for (std::chrono::seconds (1 )); std::shared_ptr<Base> lp = p; { static std::mutex io_mutex; std::lock_guard<std::mutex> lk (io_mutex) ; std::cout << "local pointer in a thread:\n" << " lp.get() = " << lp.get () << ", lp.use_count() = " << lp.use_count () << '\n' ; } }void copy (std::shared_ptr<Base> p) { std::cout << "==================================" << '\n' ; std::cout << "call copy" << '\n' ; std::shared_ptr<Base> lp = p; std::cout << "current p use_count = " << p.use_count () << '\n' ; std::cout << "exist copy" << '\n' ; std::cout << "==================================" << '\n' ; }int main () { std::shared_ptr<Base> p = std::make_shared<Base>(); std::cout << "Created a shared Derived (as a pointer to Base)\n" << " p.get() = " << p.get () << ", p.use_count() = " << p.use_count () << '\n' ; std::thread t1 (thr, p) , t2 (thr, p) , t3 (thr, p) ; copy (p); std::cout << "current p.use_count() = " << p.use_count () << '\n' ; p.reset (); std::cout << '\n' ; std::cout << "Shared ownership between 3 threads and released\n" << "ownership from main:\n" << " p.get() = " << p.get () << ", p.use_count() = " << p.use_count () << '\n' ; t1.join (); t2.join (); t3.join (); std::cout << "All threads completed, the last one deleted Derived\n" ; }
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Base::Base() Created a shared Derived (as a pointer to Base) p.get() = 0x80003a040, p.use_count() = 1 ================================== call copy current p use_count = 6 exist copy ================================== current p.use_count() = 4 Shared ownership between 3 threads and released ownership from main: p.get() = 0, p.use_count() = 0 local pointer in a thread: lp.get() = 0x80003a040, lp.use_count() = 6 local pointer in a thread: lp.get() = 0x80003a040, lp.use_count() = 4 local pointer in a thread: lp.get() = 0x80003a040, lp.use_count() = 2 Base::~Base() All threads completed, the last one deleted Derived