1. unique_ptr 1.1 初始化方式
直接初始化unique <T> myPtr(new T); //ok。但不能通过隐式转换来构造,如unique <T> myPtr = new T()。因为unique_ptr构造函数被声明为explicit 。
移动构造1 unique <T> myOtherPtr = std ::move(myPtr);但不允许复制构造,如unique <T> myOther = myPtr; 因为unique 是个只移动类型。
通过make_unique构造1 unique<T> myPtr = std::make_unique<T> ();
通过reset重置1 如std::unique_ptr up; up.reset (new T ());
1.2 指定删除器
unique_ptr<T,D> u1(p,d);删除器是unique_ptr类型的组成部分,可是普通函数指针或lambda表达式。注意,当指定删除器时需要同时指定其类型,即D不可省略。
使用默认的deleter时,unique_ptr对象和原始指针的大小是一样的。当自定义deleter时,如果deleter是函数指针,则unique_ptr对象的大小为8字节。对于函数对象的deleter,unique_ptr对象的大小依赖于存储状态的多少,无状态的函数对象(如不捕获变量的lambda表达式),其大小为4字节。
1.3 源码分析
unique_ptr的构造函数被声明为explicit,禁止隐式类型转换的行为。原因如下:1 2 ①可减少误将智能指针指向栈对象的情况。如unique_ptr<int> ui = &i ;其中的i为栈变量。 ②可避免将一个普通指针传递给形参为智能指针的函数。假设,如果允许将裸指针传给void foo(std::unique_ptr<T> )函数,则在函数结束后会因形参超出作用域,裸指针将被delete的误操作。
unique_ptr的拷贝构造和拷贝赋值均被声明为delete。因此无法实施拷贝和赋值操作,但可以移动构造和移动赋值。
删除器是unique_ptr类型的一部分。默认为std::default_delete,内部是通过调用delete来实现。
unique_ptr可以指向数组,并重载了operator []运算符。如unique_ptr<int[]> ptr(new int[10]); ptr[9]=9;但建议使用使作std::array、std::vector或std::string来代替这种原始数组。
1.4 常用操作
get():返回unique_ptr中保存的裸指针
reset():重置unique_ptr。
release():放弃对指针的控制权,返回裸指针,并将unique_ptr自身置空。通常用来初始化另一个智能指针。
swap(q):交换两个智能指针所指向的对象。
1.5 基本用法示例 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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 using namespace std; class Widget {};// 返回值RVO优化: unique_ptr<int> func () { unique_ptr<int> up(new int(100 )); return up; //u p是个左值,调用拷贝构造给返回值? No。 // C++标准要求当RVO被允许时,要么消除拷贝,要么隐式地把std::move用在要返回的局部 // 对象上去。这里编译器会直接在返回值位置创建up对象。因此根本不会发生拷贝构造, //u nique_ptr本身也不能被拷贝构造。 // return unique_ptr<int>(new int(100 )); // 右值,被移动构造。 } void foo(std::unique_ptr<int> ptr) { } void myDeleter(int* p) { cout << "invoke deleter(void* p)" << endl; delete p; } int main() { // 1 . unique_ptr的初始化 // 1.1 通过裸指针创建unique_ptr(由于unique_ptr的构造函数是explicit的,必须使用直接初始化,不能做隐式类型转换) std::unique_ptr<Widget> ptr1(new Widget); // ok; 直接初始化 // std::unique_ptr<Widget> ptr1 = new Widget(); // error。不能隐式将Widget*转换为unqiue_ptr<Widget>类型。 std::unique_ptr<int[]> ptr2(new int[10 ]); // 指向数组 // 1.2 通过移动构造 // std::unique_ptr<Widget> ptr3 = ptr1; // error,unique_ptr是独占型,不能复制构造 std::unique_ptr<Widget> ptr3 = std::move(ptr1); // ok,unique_ptr是个只移动类型,可以移动构造 auto ptr4 = std::move(ptr3); // ok, ptr4为unique_ptr<Widget>类型 // 1.3 通过std::make_unique来创建 auto ptr5 = std::make_unique<int>(10 ); // auto ptr6 = std::make_unique<vector<int>>({1 ,2 ,3 ,4 ,5 }); // error,make_unique不支持初始化列表 auto initList = { 1 ,2 ,3 ,4 ,5 }; auto ptr6 = std::make_unique<vector<int>>(initList); // 2 . 传参和返回值 int* px = new int(0 ); // foo(px); // error,px无法隐式转为unique_ptr。可防止foo函数执行完毕后,px会自动释放。 // foo(ptr5); // error,智能指针不能被拷贝。因此,可以将foo的形参声明为引用,以避免所有权转移 foo(std::move(ptr5)); // ok,通过移动构造 auto ptr7 = func (); // 移动构造 // 3 .常用操作 std::unique_ptr<Widget> upw1; // 空的unique_ptr upw1.reset(new Widget); std::unique_ptr<Widget> upw2(new Widget); cout <<"before swap..." << endl; cout << "upw1.get() = " << hex << upw1.get() << endl; cout << "upw2.get() = " << hex << upw2.get() << endl; cout << "after swap..." << endl; upw1.swap(upw2); // 交换指针所指的对象 cout << "upw1.get() = " << hex << upw1.get() << endl; cout << "upw2.get() = " << hex << upw2.get() << endl; //u pw1.release(); // release放弃了控制权不会释放内存,丢失了指针 Widget* pw = upw1.release();// 放弃对指针的控制 delete pw; // 需手动删除 if (upw1) { //u nique_ptr重载了operator bool() cout << "upw1 owns resourse" << endl; }else { cout << "upw1 lost resourse" << endl; } upw1.reset(upw2.release()); // 转移所有权 cout << "upw1.get() = " << hex << upw1.get() << endl; cout << "upw2.get() = " << hex << upw2.get() << endl; //u pw1 = nullptr; // 释放upw1指向的对象,并将upw1置空 //u pw1.reset(nullptr); // 4 .unique_ptr的大小 std::unique_ptr<int,decltype(&myDeleter)> upd1(new int(0 ), myDeleter); // 自定义删除器 auto del = [](auto* p) {delete p; }; std::unique_ptr<int, decltype(del)> upd2(new int(0 ), del); cout << sizeof(upw1) << endl; // 4 字节,默认删除器 cout << sizeof(upd1) << endl; // 8 字节 cout << sizeof(upd2) << endl; // 4 字节 return 0 ; }
1.6 使用场景 1.6.1 作为工厂函数的返回类型 1. 工厂函数负责在堆上创建对象,但是调用工厂函数的用户才会真正去使用这个对象,并且要负责这个对象生命周期的管理。所以使用unique_ptr是最好的选择。
2. unique_ptr转为shared_ptr很容易,作为工厂函数本身并不知道用户希望所创建的对象的所有权是专有的还是共享的,返回unique_ptr时调用者可以按照需要做变换。
1.6.2 PImpl机制:(Pointer to Implemention) 1. 操作方法
(1)将曾经放在主类中的数据成员放到实现类中去,然后通过指针间接地访问那些数据成员。此时主类中存在只有声明而没有定义的类型(也叫非完整类型),如Widget::Impl。
(2)在实现类中,动态分配和归还原那些原本应在主类中定义的那数据成员对象。即将这个数据成员放到实现类中定义(动态分配其内存)
2. 注意事项
(1)PImpl机制通过降低类的客户和类实现者之间的依赖性,减少了构建遍数。
(2)对于采用std::unique_ptr来实现的PImpl指针,须在类的头文件中声明特殊成员函数,但在实现文件中实现它们(注意,不能直接在头文件中实现,具体原因见《编程实验》中的说明)。如,必须同时声明并实现类的析构函数。再由于自定义了析构函数,编译器不再提供默认的移动构造和移动赋值函数,如果需要这些函数,则也必须在头文件中声明,并在实现类中去实现。
(3)上述建议仅适用于std::unique_ptr,但并不适用于std::shared_ptr。因为删除器在unique_ptr中是其类型的一部分,而在shared_ptr中则不是。声明对象时,unique_ptr支持T是个非完整类型,但在析构时T必须己经是个完整的类型。unique_ptr析构时会先判断T是否为完整类型再调用delete删除其所指对象,但shared_ptr则不会。
16.3 场景实验 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 // 1 .传统的做法// 问题:数据成员会导致Widget.h文件必须include <string>// <vector>和gadget.h。当客户包含Widget.h里,会增加编译时间,而且// 如果其中的某个头文件(如Gadget.h)发生改变,则Widget的客户必须重新编译!// class Widget// {// std::string name;// std::vector<double> data;// Gadget g1, g2, g3;// // 自定义类型,位于gadget.h。// public:// Widget();// };// 2 . 采用PImpl手法 class Widget { // 声明实现结构体以及指向它的指针 struct Impl; // 注意只有声明,没实现。是个非完整类型。 std::unique_ptr<Impl> pImpl; // 使用智能指针而非裸指针。这里声明一个指针非完整类型的指针。注意针对非完整 // 类型,可以做的事情极其有限。由于unique_ptr中会将删除器作为其类型的一部分 // 因此,但unique_ptr析构被调用时,当delete 其所指对象时,会先判断T是否是个完 // 整类型。如果不是,则会报错。因此必须在pImpl被析构前,确保Impl被定义(即是个完整类型) // 因此,使用unique_ptr<非完整类型时>,必须为该类同时定义析构函数!具体原因见后面的分析。 // std::shared_ptr<Impl> pImpl; // 由于删除器不是shared_ptr类型的组成部分。当pImpl被析构时,不会判断T是否为完整类型。 // 因此,不要求Widget必须自定义析构函数。 public: Widget(); ~Widget(); // Impl是个非完整类型,这里必须声明析构函数,并在Widget.cpp中实现它。 // 注意,不能在该文件中实现,因为此时unique_ptr看到的Impl是个非完整类型,unique_ptr内部要求delete 前,其 // 其指向的必须是个完整类的指针。 // 移动构造和移动赋值(由于自定义了析构函数,所以编译器不再提供默认的移动构造和移动赋值函数,这里需手动填加) Widget(Widget&& rhs); // 只能声明,须放在.cpp中去实现。编译器会在move构造函数内抛出异常的事件中生成析构pImpl代码, // 而此处Impl为非完整类型。 Widget& operator=(Widget&& rhs); // 只能声明,须放在.cpp中去实现。因为移动赋值pImpl时,需要先析构pImpl所指对象,但 // 此时仍为非完整类型。 // 让Widget支持复制操作。注意unique_ptr不可复制 Widget(const Widget& rhs); // 仅声明 Widget& operator=(const Widget& rhs); // 仅声明 }; Widget.h
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 #include "Widget.h" #include <string> #include <vector> class Gadget { }; struct Widget : :Impl { std::string name; std::vector<double > data; Gadget g1, g2, g3; }; Widget::Widget ():pImpl (std::make_unique<Impl>()) {} Widget::~Widget () {} Widget::Widget (Widget&& rhs) = default ; Widget& Widget::operator =(Widget&& rhs) = default ; Widget::Widget (const Widget& rhs):pImpl (std::make_unique<Impl>(*rhs.pImpl)) { } Widget& Widget::operator =(const Widget& rhs) { *pImpl = *rhs.pImpl; return *this ; }
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 #include <iostream> #include <memory> #include <functional> #include "Widget.h" using namespace std; enum class InvestmentType {itSock, itBond, itRealEstate};class Investment { public: virtual ~Investment() {} };class Stock : public Investment {};class Bond : public Investment {}; class RealEstate : public Investment {}; void makeLogEntry(Investment* pInvmt ) {} template<typename... Ts> auto makeInvestment(Ts&& ... params ) { auto delInvmt = [] (Investment* pInvmt) { makeLogEntry(pInvmt ) ; delete pInvmt; }; std::unique_ptr<Investment, decltype(delInvmt)> pInv(nullptr , delInvmt ) ; if (1 ) { pInv.reset(new Stock(std ::forward <Ts>(params ) ...)); } else if (0 ) { pInv.reset(new Bond(std ::forward <Ts>(params ) ...)); } else if (0 ) { pInv.reset(new RealEstate(std ::forward <Ts>(params ) ...)); } return pInv; }int main() { std::shared_ptr<Investment> sp = makeInvestment() ; Widget w; }
2. shared_ptr 2.1 shared_ptr 的基本用法 2.1.1 与unique_ptr的比较
比较
shared_ptr
unique_ptr
备注
初始化
①shared_ptr sp; sp.reset(new T());②shared_ptr sp(new T());③shared_ptr sp1 = sp; **//**拷贝构造 ④auto sp = make_shared(10);
①unique_ptr up; up.reset(new T());②unique_ptr up(new T());③unique_ptr up1 = std::move(up);**//**移动构造 ④auto up = make_unique(10);
两者的构造函数将声明为explicit,即不允许隐式类型转换, 如shared_ptr sp = new int(10);
条件判断
如,if(sp){…}
如,if(up){…}
两都均重载operator bool()
解引用
*sp
*up
解引用,获得它所指向的对象
->mem
sp->mem
up->mem
重载->运算符
get()
sp.get()
up.get()
返回智能指针中保存的裸指针,要小心使用。
p.swap(q)
sp.swap(q);
up.swap(q);
交换p和q指针
独有操作
①shared_ptr p(q);//拷贝构造②p = q;//赋值③p.unique();若p.use_count()为1,返回true,否则返回false。④p.use_count()//返回强引用计数
①up=nullptr;释放up指向的对象,并将up置空。②up.release();//up放弃对指针的控制权,返回裸指针,并将up置空 ③up.reset();释放up指向的对象。 up.reset(q);其中q为裸指针。令up指向q所指对象。 up.reset(nullptr);置空
注意:①unique_ptr不可拷贝和赋值,但可以被移动 。②release会切断unique_ptr和它原来管理的对象间的联系。通常用来初始化另一个智能指针 。
2.1.2 指定删除器
shared_ptr sp1(q, deleter1);与unique_ptr不同,删除器不是shared_ptr类型的组成部分 。假设,shared_ptr sp2(q,deleter2),尽管sp1和sp2有着不同的删除器,但两者的类型是一致的,都可以被放入vector<shared_ptr>类型的同一容器里。
与std::unique_ptr不同,自定义删除器不会改变std::shared_ptr的大小。其始终是祼指针大小的两倍 。
当使用shared_ptr管理动态数组 时,需要指定删除器。因为默认删除器不支持数组对象。如shared_ptr sp(new int[10], std::default_delete<**int[]**>);
数组删除器示例1:
1 2 3 4 5 6 int main() { shared_ptr<int > ptr(new int [10 ], default_delete<int []>()); // shared_ptr<int > ptr(new int [10 ], [](int * p) {delete []p; }); return 0 ; }
对象删除器示例2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <iostream> #include <memory> using namespace std;void deleteIntPtr (int * p) { delete p; cout << "int 型内存被释放了..." ; }int main () { shared_ptr<int > ptr (new int (250 ), deleteIntPtr) ; return 0 ; }
删除器可以是普通函数、函数对象和lambda表达式等。默认的删除器为std::default_delete,其内部是通过delete来实现功能的。
2.2 剖析 std::shared_ptr 2.2.1 std::shared_ptr 的内存模型
1. shared_ptr包含了一个指向对象的指针和一个指向控制块的指针 。每一个由std::shared_ptr管理的对象都有一个控制块 ,它除了包含引用计数之外,还包含了自定义删除器的副本和分配器的副本以及其他附加数据 。 2. 控制块的创建规则: (1)std::make_shared总是创建一个控制块 。
(2)从具备所有权的指针出发构造一个std::shared_ptr时,会创建一个控制块 。(如std::unique_ptr转为shared_ptr时会创建控制块,因为unique_ptr本身不使用控制块,同时unique_ptr置空)
(3)当std::shared_ptr构造函数使用裸指针作为实参时,会创建一个控制块 。这意味从同一个裸指针出发来构造不止一个std::shared_ptr时会创建多重的控制块,也意味着对象会被析构多次。如果想从一个己经拥有控制块的对象出发创建一个std::shared_ptr,可以传递一个shared_ptr或weak_ptr而非裸指针作为构造函数的实参,这样则不会创建新的控制块。
3. 引用计数(强引用计数)
(1)shared_ptr的构造函数会使该引用计数递增 ,而析构函数会使该计数递减 。但移动构造时表示从一个己有的shared_ptr移动构造到一个新的shared_ptr。这意味着一旦新的shared_ptr产生后,原有的shared_ptr会被置空,其结果是引用计数没有变化。
(2)复制赋值同时执行两种操作 (如sp1 和sp2是指向不同对象的shared_ptr,则sp1 = sp2时,将修改sp1使得其指向sp2所指的对象。而最初sp1所指向的对象的引用计数递减,同时sp2所指向的对象引用计数递增)
(3)reset函数,如果不带参数时,则引用计数减1。如果不带参数时,如sp.reset(p)则sp原来指向的对象引用计数减1,同时sp指向新的对象(p)
(4)如果实施一次递减后最后的引用计数变成0,即不再有shared_ptr指向该对象,则会被shared_ptr析构掉 。
(5)引用计数的递增和递减是原子操作 ,即允许不同线程并发改变引用计数。
shared_ptr的陷阱分析
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 83 84 85 86 87 88 89 #include <iostream> #include <vector> #include <memory> using namespace std;class Widget { };void func (shared_ptr<Widget> sp) {}int funcException () { return 0 ; } void demo (shared_ptr<int > sp, int f) {}int main () { auto pw = new Widget; std::shared_ptr<Widget> spw1 (pw) ; auto sp = new Widget; func (shared_ptr<Widget>(sp)); std::shared_ptr<Widget> spw3 (spw1) ; std::shared_ptr<Widget> spw4 (new Widget) ; std::shared_ptr<Widget> spw5 = std::make_shared<Widget>(); demo (shared_ptr<int >(new int (100 )), funcException ()); auto p1 = std::make_shared<int >(100 ); demo (p1, funcException ()); auto deleter1 = [](Widget* pw) {cout << "deleter1" << endl; delete pw; }; auto deleter2 = [](Widget* pw) {cout << "deleter2" << endl; delete pw; }; std::shared_ptr<Widget> pw1 (new Widget, deleter1) ; std::shared_ptr<Widget> pw2 (new Widget, deleter2) ; std::shared_ptr<Widget> pw3 (pw1) ; pw3.reset (new Widget); vector<std::shared_ptr<Widget>> vecs; vecs.emplace_back (pw1); vecs.emplace_back (pw2); cout << sizeof (spw1) << endl; cout << sizeof (pw1) << endl; std::shared_ptr<int > pArray1 (new int [10 ], [](int * p) {delete [] p; }) ; std::shared_ptr<int > pArray2 (new int [10 ], std::default_delete<int []>()) ; cout << pw1.use_count () << endl; if (pw1) { cout << "pw1.use_count >= 1" << endl; } else { cout << "pw1.use_count == 0" << endl; } int * p = new int (10 ); std::shared_ptr<int > a (new int (20 )) ; std::shared_ptr<int > b (a, p) ; cout << *a << endl; cout << *b << endl; return 0 ; }
2.2.2 enable_shared_from_this 模板的分析 (一)模板分析(以boost::enable_shared_from_this为例)
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 template<class T> class enable_ shared _f rom_ this { protected: enable_ shared _f rom_ this () BOOST_ NOEXCEPT { } enable_ shared _f rom_ this (enable_ shared _f rom_ this const &) BOOST_ NOEXCEPT { } enable_ shared _f rom_ this & operator=(enable_ shared _f rom_ this const &) BOOST_ NOEXCEPT { return *this ; } ~enable_ shared _f rom_ this () BOOST_ NOEXCEPT { } public: shared _p tr<T> shared _f rom_ this () { shared _p tr<T> p( weak_ this _ ); BOOST_ ASSERT( p.get() == this ); return p; } shared _p tr<T const> shared _f rom_ this () const { shared _p tr<T const> p( weak_ this _ ); BOOST_ ASSERT( p.get() == this ); return p; } public: template<class X, class Y> void _ internal_ accept_ owner( shared _p tr<X> const * ppx, Y * py ) const { if ( weak_ this _ .expired() ) { weak_ this _ = shared _p tr<T>( *ppx, py ); } } private: mutable weak_p tr<T> weak_ this _ ; }; boost::enable_ shared _f rom_ this
enable_shared_from_this模板类提供两个public属性的shared_from_this成员函数。这两个函数内部会通过weak_this_(weak_ptr类型)成员来创建shared_ptr。
internal_accept_owner函数不能手动调用,这个**函数会被shared_ptr自动调用,该函数是用来初始化唯一的成员变量weak_this **。
根据对象生成顺序,先初始化基类enable_shared_from_this,再初始化派生类对象本身。这时对象己经生成,但weak_this_成员还未被初始化,最后应通过shared_ptr sp(new T())等方式调用shared_ptr构造函数(内部会调用_internal_accept_owner)来初始化weak_this_成员 。而如果在调用shared_from_this函数之前weak_this_成员未被初始化,则会通过ASSERT报错提示。
** 使用说明**
基类必须为enable_shared_from_this,其中T为派生类的类名 。(这种方法叫奇妙递归模板模式)
通过调用shared_from_this()成员函数获得一个和this指针指向相同对象的shared_ptr。
从内部实现看,shared_from_this会查询当前对象的控制块,并创建一个指向该控制块的新shared_ptr。这样的设计就要求当前对象己有一个与其关联的控制块。为了实现这一点,就必须有一个己经存在指向当前对象的std::shared_ptr,如果不存在,则通常shared_from_this会抛出异常。
从enable_shared_from_this继承的类,在构造函数中不能调用shared_from_this()获取智能指针,因为构造过程中weak_this_成员还未被初始化,所以会报异常 what(): bad_weak_ptr
安全地从this指针创建shared_ptr
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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 #include <iostream> #include <vector> #include <memory> using namespace std;class Test1 { public : ~Test1 () { cout <<"~Test1()" << endl; } std::shared_ptr<Test1> getObject () { shared_ptr<Test1> pTest (this ) ; return pTest; } };class Test2 : public std::enable_shared_from_this<Test2> {public : ~Test2 () { cout << "~Test2()" << endl; } std::shared_ptr<Test2> getObject () { return shared_from_this (); } };class Test3 : public std::enable_shared_from_this<Test3> {public : Test3 () { } void process () { std::shared_ptr<Test3> sp = shared_from_this (); } };class Test4 : public std::enable_shared_from_this<Test4> { Test4 () {} public : template <typename ... Ts> static std::shared_ptr<Test4> create (Ts&& ... params) { std::shared_ptr<Test4> ret (new Test4(params...)) ; return ret; } void process () { std::shared_ptr<Test4> sp = shared_from_this (); } };class Widget ; std::vector<std::shared_ptr<Widget>> processWidgets; class Widget : public std::enable_shared_from_this<Widget> {public : void process () { processWidgets.emplace_back (shared_from_this ()); } ~Widget () { cout <<"~Widget()" << endl; } };int main () { { } { std::shared_ptr<Test2> pt1 (new Test2()) ; std::shared_ptr<Test2> pt2 = pt1->getObject (); } { Test3 t; Test3* pt = new Test3 (); delete pt; std::shared_ptr<Test3> spt (new Test3) ; spt->process (); std::shared_ptr<Test4> spt2 = Test4::create (); spt2->process (); } { std::shared_ptr<Widget> sp (new Widget) ; sp->process (); } return 0 ; }
2.3 dynamic_pointer_cast 的使用
dynamic_pointer_cast用于将子类的shared_ptr对象转为父类的 shared_ptr 对象,成功转换后的对象new_base和原始父对象base共享引用计数.
成功转后base对象的引用计数加一.
转换后的new_base对象的reset将导致原始对象的引用计数减一.且new_base的内容为NULL
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 #include <iostream> #include <stdio.h> #include <memory> class base { public : base (){ } virtual ~base () { } };class classC : public base {public : classC () {} virtual ~classC () {} };void test_cast_fun (std::shared_ptr<base> new_base, std::shared_ptr<classC> &child) { printf ("new_base.count=%d, child.count=%d\n" , new_base.use_count (), child.use_count ()); new_base.reset (); printf ("after new_base.reset new_base.count=%d, child.count=%d, new_base=%s \n" , new_base.use_count (), child.use_count (), NULL ==new_base?"NULL" :"NOT_NULL" ); }int main (int , char *[]) { auto c = std::make_shared<classC>(); printf ("dynamic_pointer_cast 前 child.count=%d\n" , c.use_count ()); test_cast_fun (std::dynamic_pointer_cast<base>(c), c); printf ("执行 reset 后 child=%s, c.count=%d\n" , c==NULL ?"NULL" :"NOT NULL" , c.use_count ()); return 0 ; }
输出结果为
1 2 3 4 dynamic_pointer_cast 前 child.count =1 new_base.count =2, child.count =2 after new_base.reset new_base.count =0, child.count =1, new_base =NULL 执行 reset 后 child =NOT NULL , c.count =1
2.4 使用总结
尽可能避免将裸指针传递给一个std::shared_ptr的构造函数 ,常用的替代手法是使用std::make_shared。
如果必须将一个裸指针传递给shared_ptr的构造函数,就直接传递new运算符的结果,而非传递一个裸指针变量 。如shared_ptr spw (new Widget, logginDel);
不要将this指针返回给shared_ptr 。当希望将this指针托管给shared_ptr时,类需要继承自std::enable_shared_from_this,并且从shared_from_this()中获得shared_ptr指针。
自定义删除器的shared_ptr对象在销毁函数中不能再调用自己的 shared_from_this 函数, 否则会引起崩溃
不建议使用malloc()
分配内存来创建shared_ptr
(或任何包含构造函数的对象),因为构造函数不会被调用:malloc()
仅分配内存,不会调用对象的构造函数。因此,如果使用 malloc()
创建包含 shared_ptr
或其他 C++ 对象的结构体,这些对象将不会被正确初始化。new
操作符则确保调用了构造函数,这样对象就可以被正确初始化。
可以将shared_ptr 对象直接传递给函数参数const sahred_ptr & 来防止对象和对象内容被修改
当shared_ptr对象作为set存储内容时,可以按以下方式来实现按内容的opereator<排序1 2 3 4 5 6 7 8 9 10 template <typename T>struct set_shardptr_compare { bool operator () (const std::shared_ptr<T>& lhs, const std::shared_ptr<T>& rhs) const { return *lhs < *rhs; } };typedef std::set<std::shared_ptr<xxx>, set_shardptr_compare<xxx>> SET_SHARE; SET_SHARE m_;
3. weak_ptr 3.1. weak_ptr的概况 3.1.1 weak_ptr的创建 1. 直接初始化:weak_ptr wp(sp); //其中sp为shared_ptr类型
2. 赋值: wp1 = sp; //其中sp为shared_ptr类型
wp2 = wp1; //其中wp1为weak_ptr类型
3.1.2 常用操作 1. use_count():获取当前控制块中资源的强引用计数。
2. expired():判断所观测的资源是否失效 (即己经被释放),即use_count是否为0。
(1)shared_ptr sp1 = wp.lock() ;//如果wp失效,则sp为空 (其中wp为weak_ptr类型)
(2)shared_ptr sp2(wp); //如果wp失效,则抛std::bad_weak_ptr异常 。
3. lock():获取所监视资源的shared_ptr ,如shared_ptr sp = wp.lock(); //wp为weak_ptr类型。
4. reset():重置weak_ptr,影响弱引用计数 。
3.1.3 注意事项 1. weak_ptr不是独立的智能指针,它是shared_ptr的助手,只是监视shared_ptr管理的资源是否释放,不会影响强引用计数 ,不能管理资源。
2.weak_ptr**没有重载操作符*和->**,因为它不共享指针,不能操作资源。
3.weak_ptr主要用来代替可能空悬的shared_ptr 。
weak_ptr示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> #include <memory> using namespace std;int main () { auto sp1 = make_shared<int >(10 ); weak_ptr<int > wp (sp1) ; weak_ptr<int > wp1, wp2; wp1 = sp1; wp2 = wp; auto sp2 = wp2.lock (); sp1 = nullptr ; cout << wp2.use_count () << endl; return 0 ; }
3.2 weak_ptr的应用 3.2.1 缓存对象 1. 考虑一个工厂函数loadWidget,该函数基于唯一ID来创建一些指向只读对象的智能指针。
2. 假设该只读对象需要被频繁使用,而且经常需要从文件或数据库中加载。那么可以考虑将对象缓存起来。同时为了避免过量缓存,当不再使用时,则将该对象删除。
3. 由于带缓存,工厂函数返回unique_ptr类型显然不合适。因为调用者和缓存管理器均需要一个指向这些对象的指针。
4. 当用户用完工厂函数返回的对象后,该对象会被析构,此时相应的缓存条目将会空悬。因为可以考虑将工厂函数的返回值设定为shared_ptr类型,而缓存类型为weak_ptr类型 。
3.2.2 观察者模式
观察者模式是在subject状态发生改变时,通知观察者的一种设计模式。
在多数实现中,每个subject持有指向观察者的指针 ,这使得当subject状态改变时可以很容易通知观察者。
subject 不会控制其观察者的生存期,因此应该是持有观察者的weak_ptr指针**。同时在subject的使用某个指针时,可以先确定是否空悬。
3.2.3 解决循环引用
1. A、B、C三个对象的数据结构中,A和C共享B的所有权,因此各持有一个指向B的std::shared_ptr;
2. 假设有一个指针从B指回A(即上图中的红色箭****头 ),则该指针的类型应为weak_ptr,而不能是裸指针或shared_ptr,原因如下:
①假如是裸指针,当A被析构时,由于C仍指向B,所以B会被保留。但B中保存着指向A的空悬指针(野指针),而B却检测不出来,但解引用该指针时会产生未定义行为。
②假如是shared_ptr时。由于A和B相互保存着指向对方的shared_ptr,此时会形成循环引用,从而阻止了A和B的析构。
③假如是weak_ptr,这可以避免循环引用。假设A被析构,那么B的回指指针会空悬,但B可以检测到这一点,同时由于该指针是weak_ptr,不会影响A的强引用计数,因此当shared_ptr不再指向A时,不会阻止A的析构。
3.2.4监视this智能指针 见shared_ptr中的enable_shared_from_this,其中的weak_this_指针即为weak_ptr类型,用于监视this指针。
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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 #include <iostream> #include <memory> #include <unordered_map> #include <set> using namespace std;class Widget { public : Widget (int id):ID (id){} int ID; };shared_ptr<Widget> loadWidget (int WidgetID) { return make_shared<Widget>(WidgetID); }std::shared_ptr<const Widget> fastloadWidget (int WidgetID) { static std::unordered_map<int , std::weak_ptr<const Widget>> cache; auto objPtr = cache[WidgetID].lock (); if (!objPtr) { objPtr = loadWidget (WidgetID); cache[WidgetID] = objPtr; } return objPtr; }class WeatherObservers //抽象观察者{ public : virtual void updateWeatherInfo (int num) = 0 ; };class Airport : public WeatherObservers {public : void updateWeatherInfo (int num) override { std::cout <<"Airport: " << num << endl; } };class School : public WeatherObservers {public : void updateWeatherInfo (int num) override { std::cout << "School: " << num << endl; } };class WeatherStation { using ObserverPtr = std::weak_ptr<WeatherObservers>; using ObserverList = std::set<ObserverPtr, std::owner_less<ObserverPtr>>; ObserverList obs; public : void registerObserver (const ObserverPtr oPtr) { if (obs.find (oPtr) == obs.end ()) { obs.insert (oPtr); } } void unregisterObserver (const ObserverPtr oPtr) { if (obs.find (oPtr) != obs.end ()) { obs.erase (oPtr); } } void notifyObservers (int num) { std::shared_ptr<WeatherObservers> tempPtr; for (auto & ob : obs) { if ((tempPtr = ob.lock ())) { tempPtr->updateWeatherInfo (num); } } } };int main () { WeatherStation station; std::shared_ptr<Airport> airport (new Airport()) ; std::shared_ptr<School> school (new School()) ; station.registerObserver (airport); station.registerObserver (school); station.notifyObservers (1 ); station.unregisterObserver (school); station.notifyObservers (2 ); return 0 ; }
参考 https://www.cnblogs.com/5iedu/p/11622401.html https://www.cnblogs.com/5iedu/p/11623757.html https://www.cnblogs.com/5iedu/p/11619357.html