智能指针

一、智能指针

1.1为何使用智能指针

首先我们先谈为何需要智能指针,C++11之前操作堆内存空间都是使用new,delete来维护,但是很容易造成new出来的内存忘记delete,开发人员需要大量时间维护修复。而new出来返回的指针也叫裸指针

  • 1)裸指针:直接用new返回的指针。这种指针强大,灵活,但是开发者需要全程负责维护,一不小心就容易出错,一旦用错将造成重大问题。
  • 2)智能指针:解决裸指针可能代码的各种问题。智能指针就理解成“裸指针”进行包装,给裸指针外边包了一层。它使用RAII技术,能自动根据对象的生命周期来自动释放,与unique_lock,guard_lock这些使用同样的技术

1.2 智能指针的类型

  目前C++标准库有四种智能指针,分别是auto_ptr(C++98), unique_ptr(C++11), shared_ptr(C++11),weak_ptr(C++11)。
其中:

  • 1)auto_ptr:目前auto_ptr已经完全被 unique_ptr取代,C++11中反对使用auto_ptr(弃用),绝对不能再使用。
  • 2)unique_ptr:独占式指针。同一时间只有一个指针能够指向该对象。当然,该对象的所有权还是可以移交出去,移交的方式有匿名对象(例如直接创建匿名对象赋值,或者间接使用返回值返回匿名对象),move函数
  • 3)shared_ptr:共享式指针。原理是利用引用计数的方法管理一片内存,每增加一个shared_ptr,count数加1,同理,每减少一个shared_ptr,count减1;这种引用也称为强引用。即多个指针指向同一个对象,最后一个指针被销毁时,这个对象会被释放。weak_ptr是辅助shared_ptr工作的。
  • 4)weak_ptr:看名字就知道,他是一个弱引用,他的存在是为了辅助shared_ptr的循环引用问题,它不占用引用数只能通过shared_ptr或者weak_ptr构造赋值没有重载 * 和 -> 运算符,因此不可以直接通过 weak_ptr类型访问对象,典型的用法是通过 lock() 成员函数来获得 shared_ptr,进而使用对象

二、auto_ptr

2.1 演示

该智能指针已经被弃用,下面只是演示它所有权改变后不报错误的代码,实际上你编译的时候它也会提示你auto已经被弃用,但还能运行。

 1 #include <iostream>
 2 #include <string>
 3 #include <memory>
 4 using namespace std;
 5 
 6 /*
 7       auto_ptr<>的使用 已经被C++11抛弃 
 8       1 采用所有权模式 存在潜在的内存问题  任何情况别用
 9       这里只是演示它存在的问题
10 */
11 void test01(){
12 
13     auto_ptr<string> p1(new string("Hello World")); //开辟string对象并且用string的构造赋值 然后用智能指针p1指向 
14     auto_ptr<string> p2;
15 
16     p2=p1;                          //将p1所有权给p2 p1被释放了 不能再使用
17     for(int i=0;i<p2->size();i++){
18         //cout<<p1->at(i); error
19         cout<<p2->at(i);
20     }
21     cout<<endl;
22 }
23 /*
24     总结:p2=p1后,p2夺走所有权,你看源码可以知道p1会在交换之后被释放掉了,
25     所以在使用它就会相当于非法访问,但是他不会报错,这就是auto_ptr的问题。
26 */
27 
28 
29 int main(){
30 
31     test01();
32     return 0;
33 }

结果

1)p1所有权被代替后,仍使用p1就会报错。

2)虽然更替后p2仍能使用,但存在的p1存在风险,开发人员可能不小心使用,所以绝对不能再使用auto_ptr。

2.2 问题小结

p2=p1后,p2夺走所有权,你看源码可以知道p1会在交换之后被释放掉了, 所以在使用它就会相当于非法访问,但是他不会报错,这就是auto_ptr的问题

三、shared_ptr

3.1 shared_ptr基础

  • 1)共享所有权,不是被一个shared_ptr拥有,而是被多个shared_ptr之间相互协作。shared有额外开销
  • 2)工作原理:利用引用计数的方法管理一片内存,每增加一个shared_ptr,count数加1,同理,每减少一个shared_ptr,count减1;这种引用也称为强引用
  • 3)最后一个指向该内存对象的shared_ptr在什么情况下,会释放该对象呢?
    • 这个shared_ptr被析构的时候。
    • 这个shared_ptr指向其他对象时。

3.2 shared_ptr初始化

shared_ptr的三种初始化方式:裸指针初始化、返回值初始化、make_shared函数初始化。

3.2.1 通过裸指针与返回值初始化

 1 shared_ptr<int> make(int value){
 2     return shared_ptr<int>(new int(value));
 3 }
 4 
 5 void test02(){
 6     shared_ptr<int> pi1(new int(100)); //裸指针初始化,pi指向一个值为100的int型数据
 7     shared_ptr<int> pi2 = make(200);//返回值初始化
 8 
 9     cout<< (*pi1.get()) << endl;
10     cout<< (*pi2.get()) << endl;
11 }
12 
13 int main(){
14 
15     test02();
16 
17     return 0;
18 }

3.2.2 通过make_shared函数初始化

3.2.2.1make_shared简介

  • 1)注意,裸指针可以初始化shared_ptr,但是不推荐,智能指针不要穿插用,否则会出问题。所以我们不建议使用返回值进行初始化,而是通过make_shared初始化
  • 2)make_shared是标准库里的函数模板,安全,高效的分配和使用shared_ptr,它能够在动态内存中,分配并初始化一个对象,然后返回指向此对象的shared_ptr。
 1 //1 性能更好(高效)
 2 /*
 3      ①性能更好(高效):用new来构造shared_ptr指针,那么new的过程是一次堆上面的内存分配,
 4      而在构造shared_ptr对象的时候,由于需要使用堆上面共享的引用计数(指针),又需要在堆上面
 5      分配一次内存,即需要分配两次内存,而如果用make_shared函数,则只需分配一次内存,
 6      所以性能会好很多。
 7      
 8      伪代码解释:下面可以看到,ptr传进来已经new一次,然后引用计数_pRefCount也会new一次
 9      造成两次分配内存。
10 */
11 template <class T>
12 class SharedPtr
13 {
14 public:
15     //
16     SharedPtr(T* ptr = nullptr): _ptr(ptr), _pRefCount(new int(1)), _pMutex(new mutex){
17         // 如果是一个空指针对象,则引用计数给0
18         if (_ptr == nullptr)
19             *_pRefCount = 0;
20     }
21 
22 private:
23     int*   _pRefCount; // 引用计数
24     T*     _ptr;       // 指向管理资源的指针
25     mutex* _pMutex;    // 互斥锁
26 }
27 
28 //2 更安全
29 /*
30      ②更加安全:若我们使用裸指针对shared_ptr构造时,包含两步操作:
31     (1)new一个堆内存。
32     (2)分配一个引用计数区域管理该内存空间。
33      但是并没有保证这两个步骤的原子性,当做了第(1)步,没有做第二步如果程序抛出了异常,
34      将导致内存泄露,而make_shared内部有对这两个步骤合成一步进行处理,
35      因此更推荐使用make_shared来分配内存。
36 */
37 
38 //3 make_shared缺点
39 /*
40     虽然make_shared针对裸指针更好,但它也有缺点。
41     ③缺点:make_shared一次性分配堆内存的做法,在释放的时候可能会导致内存延迟释放,
42     因为如果有weak_ptr持有了指针,引用计数不会释放,而引用计数和实际的对象分配在同一块堆内存,
43     因此无法将该对象释放,如果两块内存分开申请,则不存在这个延迟释放的问题。
44 */
45 
46 //4 总结
47 /*
48     实际开发可能不会考虑这些,使用裸指针或者make_shared都行,但是大家一定要知道它们可能存在的问题。
49 */

3.2.2.2 make_shared优势

  1、性能更好(高效):用new来构造shared_ptr指针,那么new的过程是一次堆上面的内存分配,而在构造shared_ptr对象的时候,由于需要使用堆上面共享的引用计数(指针),又需要在堆上面
分配一次内存,即需要分配两次内存,而如果用make_shared函数,则只需分配一次内存,所以性能会好很多

  2、更加安全:若我们使用裸指针对shared_ptr构造时,包含两步操作:
    (1)new一个堆内存。
    (2)分配一个引用计数区域管理该内存空间。
  但是并没有保证这两个步骤的原子性,当做了第(1)步,没有做第二步如果程序抛出了异常,将导致内存泄露,而make_shared内部有对这两个步骤合成一步进行处理,因此更推荐使用make_shared来分配内存

3.2.2.3 make_shared缺点

  1、虽然make_shared针对裸指针更好,但它也有缺点。make_shared一次性分配堆内存的做法,在释放的时候可能会导致内存延迟释放,因为如果有weak_ptr持有了指针,引用计数不会释放,而引用计数和实际的对象分配在同一块堆内存,因此无法将该对象释放,如果两块内存分开申请,则不存在这个延迟释放的问题

  2、此外,需要注意,shared_ptr不支持隐式转换。

1 //例1
2 shared_ptr<int> pi2 = new int(200); //不可以,智能指针是explicit,不可以进行隐式类型转换,必须直接初始化形式
3 
4 //例2
5 shared_ptr<int> make(int value){
6     return new int(value);   //error,无法把new得到的int *转换成shared_ptr
7     return shared_ptr<int>(new int(value))//正确写法
8 }

3.2.2.4 make_shared使用

在上面介绍为何使用make_shared顶替裸指针初始化后,下面正式说明make_shared如何初始化shared_ptr。

 1 #include <iostream>
 2 #include <string>
 3 #include <memory>
 4 using namespace std;
 5 
 6 shared_ptr<int> make(int value){
 7     return shared_ptr<int>(new int(value));
 8 }
 9 
10 void test02(){
11     // shared_ptr<int> pi1(new int(100)); //pi指向一个值为100的int型数据
12     // shared_ptr<int> pi2 = make(200);
13     // cout<< (*pi1.get()) << endl;
14     // cout<< (*pi2.get()) << endl;
15 
16     shared_ptr<int> p1 = make_shared<int>(100);
17     shared_ptr<string> p2 = make_shared<string>(5, 'a'); //类似于string mystr(5, 'a')
18     shared_ptr<int> p3 = make_shared<int>();//默认初值为0,cout<< (*p3.get()) << endl;
19     p3 = make_shared<int>(300); //指向新int,p3首先释放指向值为0的内存,然后指向这个新的300的内存
20     auto p4 = make_shared<string>("I love you");
21 
22     cout<< (*p1.get()) << endl;
23     cout<< p2.get()->data() << endl;
24     cout<< (*p3.get()) << endl;
25     cout<< p4.get()->data() << endl;
26 }
27 
28 int main(){
29 
30     //test01();
31     test02();
32 
33     return 0;
34 }

结果

3.3 shared_ptr的引用计数

  引用计数就是共用一片内存的shared_ptr个数。当一个shared_ptr开辟一个内存,引用计数为1,然后用该shared_ptr初始化其它shared_ptr那么就是共用一个内存,引用计数为2,以此类推。而当一个shared_ptr指向其它shared_ptr的内存,或者shared_ptr生命周期结束被析构时,引用计数都会减少

 1 #include <iostream>
 2 #include <string>
 3 #include <memory>
 4 using namespace std;
 5 
 6 void Func1(shared_ptr<int> a)
 7 {
 8     cout<<"函数1内:"<<endl;
 9     cout<<"值的引用数为: "<<a.use_count()<<endl;          // 2 调用完毕a被释放 引用计数减1
10     cout<<"函数1结束。"<<endl;
11 }
12 
13 shared_ptr<int> Func2(shared_ptr<int>& a)
14 {
15     cout<<"函数2内:"<<endl;
16     cout<<"引用的引用数为: "<<a.use_count()<<endl;         // 1引用不会增加引用计数
17     cout<<"函数2结束。"<<endl;
18 
19     return a;
20 }
21 
22 //主要测试参数为值,引用和返回值对引用计数的影响
23 void test03(){
24     
25     shared_ptr<int> sh1(new int(10));                  // 构造一个指向int类型对象的指针sh1,引用计数为1
26     cout<<"Ref count: "<< sh1.use_count() << endl;
27 
28     {
29         shared_ptr<int> sh2 = sh1;                     
30         cout<<"Ref count: "<< sh1.use_count() << endl;
31     }
32     //sh2生命周期结束后
33     cout<<"Ref count: "<< sh1.use_count() << endl;
34     cout<<endl;
35 
36     //1 测试参数为值
37     Func1(sh1);
38     cout<<endl;
39     
40     //2 测试参数为引用
41     Func2(sh1);
42     cout<<endl;
43 
44     //3 测试接收匿名对象
45     shared_ptr<int> sh3 = Func2(sh1);
46     cout<<"sh3加入之后的引用数:"<<sh3.use_count()<<endl;
47 
48 }
49 
50 //测试引用计数增加减少
51 void test04(){
52     auto p1 = make_shared<int>(100);
53     auto p2(p1);
54     auto p3 = Func2(p2);
55     cout<<"Ref count: "<< p1.use_count() << endl;
56     p3 = make_shared<int>(200); //p3指向新对象,计数为1,p1、p2指向对象计数恢复为2;
57     p2 = make_shared<int>(300); //p2指向新对象,计数为1,p1指向对象的计数恢复为1;
58     cout<<"p3,p2改变指向后,Ref count: "<< p1.use_count() << endl;
59 
60     {
61         shared_ptr<int> p4 = p1;                     
62         cout<<"Ref count: "<< p1.use_count() << endl;
63     }
64     cout<<"p4生命周期结束后,Ref count: "<< p1.use_count() << endl;
65 }
66 
67 int main(){
68 
69     //test01();
70     //test02();
71     test03();
72 
73     cout<<endl;
74     cout<<"====test04 begin===="<<endl;
75 
76     test04();
77 
78     return 0;
79 }

结果

从上面结果可以得出:

  • 1)传值会使引用计数加1,传引用则不会
  • 2)当返回值为shared_ptr类型,若我们使用变量接收,则引用计数会加1,不接收则不加。归根结底是因为返回值时,编译器会自动创建一个匿名对象返回。例如Func2(sh1);与shared_ptr sh3 = Func2(sh1)。
  • 3)使用同一片内存为其它shared_ptr赋值,会使引用计数加1。例如sh2 = sh1或者sh2(sh1);

上面是针对于会使引用计数增加的总结。下面总结引用计数减少的。

  • 1)改变指向其它内存的shared_ptr会减1
  • 2)生命周期结束的shared_ptr会减1。当最终引用计数减为0时,就会释放该内存

3.4 shared_ptr的其它成员函数

3.4.1 use_count()函数

use_count()是获取该片内存有多个shared_ptr个对象正在引用。

3.4.2 unique()函数

unique:该智能指针是否独占某个指向的对象,也就是若只有一个智能指针指向某个对象,则unique()返回true,多个返回fasle。(为空时也不属于独享)

3.4.3 reset()与shared_ptr的比较

reset()函数分为无参与有参的使用。

  • 1)无参时,调用该函数的shared_ptr对象的引用计数为0,而该片内存并不为0,只是减1
  • 2)有参时,调用该函数的shared_ptr对象指向新的内存,引用计数加1。而原本的内存减1
  • 3)由于reset被置空后,一般需要比较,所以将shared_ptr的比较放在reset一起,当然也可以使用use_count代替比较来判断是否被置空。
  • 4)下面例子可以看到,空指针也可以通过reset重新初始化。即sh1。

看例子。

 1 //shared_ptr的比较 与 reset函数方法
 2 void test05(){
 3 
 4     shared_ptr<int> sh1=make_shared<int>(3);                 
 5     cout<<sh1.use_count()<<endl;                             // count=1
 6 
 7     shared_ptr<int> sh3=sh1;                                 
 8     cout<<sh1.use_count()<<endl;                             // count=2
 9 
10     //1 比较 重载了==与!=运算符  实际上当成指针比较就好了
11     if(sh1!=NULL && sh3!=NULL){
12         cout<<"sh1和sh3不为空!"<<endl;
13     }
14 
15     //2.1 无参reset函数  使调用的shared_ptr指向一个空资源 只是该shared_ptr的引用计数变为0 其他的因为sh1调用reset而减1 
16     sh1.reset();                                             // 使sh1指向的count为0
17     cout<<sh1.use_count()<<endl;                             // count=0
18     if (sh1 == nullptr){
19         cout << "sh1被置空" << endl;
20     }
21     cout<<sh3.use_count()<<endl;                             // count=1
22 
23     //2.2 有参reset 使shared_ptr指向参数new出的资源对象
24     sh1.reset(new int(5));                                   // sh1指向该参数new出的资源
25     cout<<sh1.use_count()<<endl;                             // count=1
26 
27 }

结果

3.4.4 解引用的意思

解引用就是获取该裸指针的对象。

1 shared_ptr<int> p(new int(123));
2 cout << *p << endl;

3.4.5 get()

get():获取裸指针操作。考虑到有些函数的参数需要的是一个内置裸指针,而不是智能指针。例如上面的初始化使用过get函数。

3.4.6 swap()

swap():交换两个智能指针所指向的对象。

1 shared_ptr<string> ps1(new string("1111111"));
2 shared_ptr<string> ps2(new string("2222222"));
3 std::swap(ps1, ps2); //交换ps1指向222222222
4 ps1.swap(ps2);    //在交换ps1指向11111111

3.5 自定义删除回调函数(删除器)

分析:当传给shared_ptr构造函数不止一个对象时,例如是一个对象数组时,因为shared_ptr析构默认用的是delete删除器,只能delete掉一个对象,所以我们需要自定义回调函数,用于析构传进的对象数组

3.5.1 使用函数模板作为自定义删除器

 1 //用来释放malloc出来的函数对象
 2 template<class T>
 3 class FreeFunc{
 4 public:
 5     void operator()(T* ptr)
 6     {
 7         cout << "free:" << ptr << endl;
 8         free(ptr);
 9     }
10 };
11 
12 //用来释放new[]出来的函数对象
13 template<class T>
14 class DeleteArrayFunc {
15 public:
16     void operator()(T* ptr)
17     {
18         cout << "delete[]" << ptr << endl;
19         delete[] ptr;
20     }
21 };
22 
23 //用来释放文件描述符的函数对象
24 template<class T>
25 class ClosefdFunc{
26 public:
27     void operator()(T* fd)
28     {
29         cout << "close fd" << fd << endl;
30         fclose(fd);
31     }
32 };
33 
34 void test06(){
35 
36     FreeFunc<int>        Object1;
37     shared_ptr<int>      sp1((int*)malloc(sizeof(int)*4), Object1);         // 回调函数是可调用对象,可以是普通的函数名或者函数对象或者lambda表达式
38 
39     DeleteArrayFunc<int> Object2;
40     shared_ptr<int>      sp2(new int[4], Object2);
41 
42     ClosefdFunc<FILE>    Object3;
43     shared_ptr<FILE>     sp3(fopen("myfile.txt","w"), Object3);
44 
45 }

结果看到,三个自定义析构函数均被调用,并且可以发现文件对象会被编译器优先释放。

3.5.2 使用其它新特性作为删除器

  • 1)可以用default_delete来做删除器,这个是标准库里的模板类。
    1 class A {
    2 public:
    3     A() {};
    4     ~A() {};
    5 };
    6  
    7 void test07(){
    8     shared_ptr<A> p(new A[10], std::default_delete<A[]>());
    9 }
  • 2)可以用C++新规则的简单写法。
    1 void test08(){
    2     shared_ptr<A[]> p1(new A[10]);//A类在上面
    3     shared_ptr<int[]> p2(new int[10]);
    4     p2[0] = 12;
    5     p2[1] = 15;
    6 
    7     cout<<p2[0]<<endl;//输出12
    8     cout<<p2[1]<<endl;//输出15
    9 }

3.5.3 模板+lambda表达式实现开发时常用的删除器

由于我们开发时很少使用到像5.1这种模板,因为比较长,且需要额外定义对象传入,所以我们开发时更倾向使用lambda表达式,代码更少。但是并不是不能使用5.1这种方法。

  • 1)不带删除数组的模板+lambda表达式删除器。
     1 template <class T>
     2 shared_ptr<T> my_make_shared(){
     3     return shared_ptr<T>(new T,[](T *ptr){delete ptr,ptr=nullptr;});
     4 }
     5 
     6 void test09(){
     7     //对于类中没有成员的,只有使用裸指针
     8     shared_ptr<int> sh1 = my_make_shared<int>();
     9     *sh1.get() = 2;
    10     cout<<*sh1.get()<<endl;
    11 
    12     //对于类中有成员的,可以直接使用智能指针
    13     shared_ptr<A> sh2 = my_make_shared<A>();
    14     sh2->SetI(100);
    15     cout<<sh2->GetI()<<endl;
    16 }
  • 2)带删除数组的模板+lambda表达式删除器。下面我将lambda表达式换成default_delete函数,因为上面已经有lambda,当然你也可以换一下。
     1 template<typename T>
     2 shared_ptr<T> my_make_shared_array(size_t size)
     3 {
     4     return shared_ptr<T>(new T[size], default_delete<T[]>());
     5 }
     6 void test10(){
     7     shared_ptr<A> parray = my_make_shared_array<A>(15);
     8     parray->SetI(200);
     9     //parray.get()[0].SetI(100);//也行,但直接使用智能指针更安全
    10     cout<<parray.get()[0].GetI()<<endl;//输出200
    11 }

3.5.4 指定删除器额外说明

  就算是两个shared_ptr指定了不同的删除器,只要他们所指向的对象类型相同,那么这两个shared_ptr也是属于同一类型,所以它们可以改变指向,改变指向后,若引用计数变为0,则先用旧的删除器删除内存,然后它将使用新的删除器

 1 auto lambda1 = [](int *p)
 2 {
 3     delete p;
 4 };
 5  
 6 auto lambda2 = [](int *p)
 7 {
 8     delete p;
 9 };
10  
11  
12 shared_ptr<int> p1(new int(100), lambda1);
13 shared_ptr<int> p2(new int(100), lambda2);
14 p2 = p1; //p2会先调用lambda2把自己所指向的对象释放,然后指向p1所指向的对象。p1所指向 
15          //的对象引用计数为2,整个main执行完成后还会调用lambda1来释放p1,p2共同指向的 
16          //对象

3.5.5小结

并且注意,我们5.3常用删除迭代器为何不使用make_shared,因为使用make_shared这种方法我们无法指定自己的删除器。所以只能使用new。

3.6 enable_shared_from_this模板类详解

  什么时候该使用enable_shared_from_this模板类?当我们需要一个类对象返回本身并且该类使用了shared_ptr智能指针时,就需要使用enable_shared_from_this。并且需要注意,当我们使用智能指针管理资源时,必须统一使用智能指针,而不能再某些地方使用智能指针,某些地方使用原始指针,否则不能保持智能指针的语义,从而产生各种错误

3.6.1 错误使用this返回对象本身

错误代码如下:

 1 class Test
 2 {
 3 public:
 4     //析构函数
 5     ~Test() { std::cout << "Test Destructor." << std::endl; }
 6 
 7     //获取指向当前对象的指针
 8     shared_ptr<Test> GetObject(){
 9         return shared_ptr<Test>(this);
10     }
11 };
12 
13 void test11(){
14     {
15         shared_ptr<Test> p(new Test());
16         shared_ptr<Test> q = p->GetObject();
17         cout<<endl;
18     }
19 }

结果,原因是析构了两次new出来的Test:

  为什么会这样呢?原因出在GetObject函数。它里面使用this给shared_ptr赋值,这就会导致原本shared_ptr p(new Test())时,该内存已经被p管理。当调用至shared_ptr(this),返回的匿名对象会被编译器再次用其创建新的匿名对象返回,然而,该匿名对象是由裸指针this赋值的这就导致p与q各自引用相同的内存,但是引用计数都是1,因为p与q这两个shared_ptr是没有任何关系的,所以当p与q生命周期结束后,必定使同一片内存被释放两次导致报段错误
实际上上面的错误就是下面例子。

1 int *p = new int(12);
2 shared_ptr<int> p1(p);
3 shared_ptr<int> p2(p);//必定报错,原因是两个没有任何关系的shared_ptr绑定同一片内存,导致释放两次。

3.6.2 改正this,使用enable_shared_from_this模板类返回对象本身

  上面是错误的绑定this来获取shared_ptr即获取对象本身。那么有什么方法从一个类的成员函数中获取当前对象的shared_ptr呢,其实方法很简单:只需要该类继承至enable_shared_from_this模板类,然后调用该基类enable_shared_from_this中的shared_from_this即可。
正确代码:

 1 class Test: public enable_shared_from_this<Test>//修改1
 2 {
 3 public:
 4     //析构函数
 5     ~Test() { std::cout << "Test Destructor." << std::endl; }
 6 
 7     //获取指向当前对象的指针
 8     shared_ptr<Test> GetObject(){
 9         //return shared_ptr<Test>(this);
10         return shared_from_this();//修改2
11     }
12 };
13 
14 void test11(){
15     {
16         shared_ptr<Test> p(new Test());
17         shared_ptr<Test> q = p->GetObject();
18         cout<<endl;
19     }
20 }

那为什么继承enable_shared_from_this后就可以正常使用呢?所以我们需要分析enable_shared_from_this这个类了,继续往下看。

3.6.3 分析enable_shared_from_this源码

为了方便理解,我们使用boost库的enable_shared_from_this类模板源码,实际上标准库的也差不多,标准库使用友元处理,而boost库使用public处理。
enable_shared_from_this.hpp文件,enable_shared_from_this模板类的实现如下:

 1 template<class T> class enable_shared_from_this
 2 {
 3 protected:
 4     enable_shared_from_this() BOOST_NOEXCEPT
 5     {
 6     }
 7     enable_shared_from_this(enable_shared_from_this const &) BOOST_NOEXCEPT
 8     {
 9     }
10     enable_shared_from_this & operator=(enable_shared_from_this const &) BOOST_NOEXCEPT
11     {
12         return *this;
13     }
14     ~enable_shared_from_this() BOOST_NOEXCEPT // ~weak_ptr<T> newer throws, so this call also must not throw
15     {
16     }
17 public:
18     shared_ptr<T> shared_from_this()
19     {
20         shared_ptr<T> p( weak_this_ );
21         BOOST_ASSERT( p.get() == this );
22         return p;
23     }
24     shared_ptr<T const> shared_from_this() const
25     {
26         shared_ptr<T const> p( weak_this_ );
27         BOOST_ASSERT( p.get() == this );
28         return p;
29     }
30 public: // actually private, but avoids compiler template friendship issues
31     // Note: invoked automatically by shared_ptr; do not call
32     template<class X, class Y> void _internal_accept_owner( shared_ptr<X> const * ppx, Y * py ) const
33     {
34         if( weak_this_.expired() )
35         {
36             weak_this_ = shared_ptr<T>( *ppx, py );
37         }
38     }
39 private:
40     mutable weak_ptr<T> weak_this_;
41 };

  首先我们看到它有三个构造函数,而实际的public公有方法即能被调用的函数只有三个,其中_internal_accept_owner还被注明不能调用。所以该类非常简单,着重看不加const的shared_from_this(或者const的shared_from_this函数)外加私有成员weak_this_即可。
然后我们分析:

  • 1)如果想调用shared_from_this返回shared_ptr,那么就必须要初始化weak_this_ ,因为shared_ptr是由weak_this_赋值创建的,这里解释一下为什么使用若引用weak_ptr类型作为成员:因为弱引用不占引用计数,若我们使用shared_ptr强引用,那么该成员就必定占用一个计数,就会导致该内存本该由1->0释放,但是由于该成员的存在导致无法释放,造成内存延时释放,与逻辑不匹配,所以必须使用弱引用作为成员
  • 2)接着上面讲,因为shared_ptr是由weak_this_赋值创建的,所以weak_this_必须被初始化,而整个类中只有_internal_accept_owner是初始化weak_this_的,该函数由shared_ptr构造时自动调用。所以目前逻辑就清楚了,只有shared_ptr构造被完整调用后,weak_this_才有值,然后shared_from_this才能被成功返回。但是注意,构造初始化的顺序刚好与这个顺序有点相反。例如:
    1 shared_ptr<Test> p(new Test());

  首先进入shared_ptr,但是会先去执行Test的构造,然后又因为enable_shared_from_this是Test的基类,所以最终先去执行完enable_shared_from_this的构造,再返回Test的构造执行完,最后返回shared_ptr的构造执行完。但是我们写代码时只需要记住必须只有shared_ptr被先执行,才能进入Test与enable_shared_from_this的构造。而不能越过shared_ptr的构造直接调用Test的构造和enable_shared_from_this的构造,这必然是错误的,因为没有shared_ptr的构造初始化weak_this_,shared_from_this返回的p肯定是非法的。

3.6.4 继承于enable_shared_from_this后常见地错误

所以我们可以根据上面所说,列出常见继承于enable_shared_from_this类后,使用错误的代码。

3.6.4.1 错误1

1 class Test : public enable_shared_from_this<Test>
2 {
3     Test() { shared_ptr<Test> pTest = shared_from_this(); }
4 };

  这种写法必然是错误的,上面已经说过,不能在构造shared_ptr完成之前调用shared_from_this,而enable_shared_from_this的构造和Test的构造必定在shared_ptr构造函数完成之前,所以shared_ptr的构造函数必定未执行完毕,也就是说weak_this_未被初始化,所以shared_from_this返回的是一个未知状态值。

有人就会说,那在pTest的构造函数之前创建一个shared_ptr不就行了吗,好,那我们就看错误2。

3.6.4.2 错误2

1 Test() {
2     shared_ptr<Test> sh(new Test());
3     shared_ptr<Test> pTest = sh->shared_from_this();
4       //shared_ptr<Test> pTest = shared_from_this(); 
5 }

  错误2这种情况实际上我真的不想列出来,当我们在Test构造中new一个Test本身,就会造成递归死循环调用该构造,根本就不会再往下执行,并且死循环过程中出现段错误,原因我猜可能是循环不断new,而没有释放内存,编译器认为出问题直接报错,因为我测试过不断new而不释放内存最终导致死机的情况。所以这种情况不再多说。

3.6.4.3 错误3

 1 class Test : public enable_shared_from_this<Test>
 2 {
 3     void func() { shared_ptr<Test> pTest = shared_from_this(); }
 4 };
 5 int main()
 6 {
 7     Test test;
 8     test.func();    //错误
 9     // Test *pTest = new Test;
10     // pTest->func(); //同理错误
11 }

  上面错误3的代码实际上错误1也是一样,都是没有执行完整shared_ptr的构造函数,导致weak_this_未被初始化。gdb调试结果也报错weak_this_未被初始化。

  正确的写法就好像我们第2大点的调用例子,必须先调用shared_ptr执行完整的shared_ptr构造函数初始化weak_this_ 后,才能调用shared_from_this。
正确写法:
  注意:func是返回void,我们只是在函数体内测试是否能获取shared_ptr pTest = shared_from_this(),实际上我们获取本身直接将shared_from_this返回即可,当然返回值也需要换换。

 1 class Test : public enable_shared_from_this<Test>
 2 {
 3     void func() { shared_ptr<Test> pTest = shared_from_this(); }
 4 };
 5 
 6 int main()
 7 {
 8     shared_ptr<Test> pTest( new Test() );
 9     pTest->func();
10 }

3.6.5 总结enable_shared_from_this的用法

  • 1)实际上enable_shared_from_this的用法非常简单,就是像第2大点那样继承,然后使用即可。只不过理解透需要点时间。
  • 2)获取本类对象本身,必须先调用shared_ptr p(new Test())执行完整个shared_ptr的构造函数,才能调用shared_from_this获取对象本身,而不能直接调用Test test这些自定义类对象的构造后,就调用shared_from_this,这是错误的

3.7 智能指针之shared_ptr易错点

3.7.1 慎用裸指针给shared_ptr赋值

3.7.1.1 例1

 1 class A {
 2 public:
 3     A() {};
 4     A(int i) {
 5         m_i=i;
 6         cout<<"A"<<endl;
 7     };
 8     void SetI(int i) {m_i=i;};
 9     int GetI(){return m_i;}
10     ~A() {
11         cout<<"~A"<<endl;
12     };
13 private:
14     int m_i;
15 };
16 
17 void proc1(shared_ptr<int> ptr)
18 {
19     return;
20 }
21 
22 void proc2(shared_ptr<A> ptr)
23 {
24     return;
25 }
26 
27 void test13()
28 {
29     int *p1 = new int(100);
30     //proc(p1); //语法错误,int *p1不能转换成shared_ptr<int>(不支持隐式转换)
31     proc1(shared_ptr<int>(p1)); 
32     *p1 = 45; //潜在的不可预料的问题,因为p已经被释放了
33 }
34 
35 void test14(){
36     A *p2 = new A(100);
37     proc2(shared_ptr<A>(p2));//A构造只会被调用一次执行完proc2
38     *p2 = 50;//错误1,系统会帮你重新开辟A对象,但是仍会报错,这就是不可预料的问题之一
39     //p2->SetI(45); //错误2,系统未重新开辟对象
40 }

  当我界面直接断点调试test13时,它执行到 *p1 = 45;没有报错,但是程序最终结束时却卡死,说明程序出现问题。然后我们又通过gdb调试一遍(命令行模式),它的错误就明显报错了。

  然后我又测试了test14,是shared_ptr存储自定义对象的。结果发现又出现不同的错误或称不可预料的错误,当p2在调完proc2函数后,p2就被释放了,我再次违法使用*p2=50时,系统会重新开辟A内存,但是最终也会报错。

而测试p2->SetI(45);错误时与test13错误一样。

3.7.1.2 例2

同上面一样,均是用一个裸指针给多个shared_ptr赋值,导致释放多次。

1 int *p = new int(12);
2 shared_ptr<int> p1(p);
3 shared_ptr<int> p2(p);   //不可以!!!!这种写法p1和p2无关联,会导致p1和p2所指向的 
4                          //内存被释放两次,产生异常

正确的用裸指针赋值应该是下面的方法:

1 shared_ptr<int> p1(new int());
2 shared_ptr<int> p2(p1);   //这种写法可以,p1与p2用的是同一个控制块,两者互通

所以我们一定要慎用用裸指针赋值,或者说,使用裸指针赋值的方式只能用上面正确的写法。

3.7.2 慎用get()返回的指针(智能指针返回对应的裸指针)

1 shared_ptr<int> myp(new int(122));
2 int *p = myp.get();
3 delete p;   //不可以释放!!!!!!!!!!!!!!!
4  
5 shared_ptr<int> myp2(p); //不可以!!!myp和myp2的引用计数都为1,实际这种错误就是上面多次使用裸指针赋值

也就是我们一旦使用智能指针管理对象,之后就最好不要对原来的裸指针进行操作,否则导致各种错误,如二次释放内存。

3.7.3 禁用使用this给shared_ptr赋值,然后返回类对象本身

这种错误本来属于第1大点的错误点,即使用一个裸指针给多个shared_ptr赋值,但是由于返回类对象本身比较重要,所以单独拿出来分析。
错误写法:

 1 class CT
 2 {
 3 public:
 4     shared_ptr<CT> getself()
 5     {
 6         return shared_ptr<CT>(this); //该this已经被一个shared_ptr管理,但是现在却又被新的shared_ptr管理,必然报错
 7     }
 8 };
 9  
10 shared_ptr<CT> pct1(new CT());
11 shared_ptr<CT> pct2 = pct1->getself(); //不可以pct1与pct2虽然指向同一内存,但是pct1和pct2两个毫无关系,引用计数均为1

  pct1与pct2虽然指向同一块内存,但是二者毫无关系,各自的引用计数都是1,所以在离开他们的作用域时都会进行内存释放,这就导致了同一内存的二次释放。

  正确写法应该是使用enable_shared_from_this类模板返回。
  enable_shared_from_this的工作原理:enable_shared_from_this有一个弱指针,当我们创建了一个share_ptr,该弱指针就会被初始化赋值,但是引用数不会加1,然后当我们调用shared_from_this时,就能返回通过weak_ptr赋值的share_ptr。这样就可以获取到对象本身

 1 class CT: enable_shared_from_this<CT>//必须继承enable_shared_from_this
 2 {
 3 public:
 4     shared_ptr<CT> getself()
 5     {
 6         return shared_from_this(); //正确写法
 7    }
 8 };
 9  
10 shared_ptr<CT> pct1(new CT());
11 shared_ptr<CT> pct2 = pct1->getself(); 

3.7.4 shared_ptr的循环引用问题

 1 //3.2 shared_ptr的循环引用
 2 class Children;    //声明
 3 
 4 class Parent
 5 {
 6 public:
 7     ~Parent()
 8     {
 9         cout << "Parent    destructor" << endl;
10     }
11 
12     shared_ptr<Children> s_children1;
13 };
14 
15 class Children
16 {
17 public:
18     ~Children()
19     {
20         cout << "Children  destructor" << endl;
21     }
22     shared_ptr<Parent> s_parent1;
23 };
24 
25 void test04()
26 {
27     shared_ptr<Parent>   s_parent(new Parent());      //1 调用拷贝对象构造 使s_parent指向Parent共享资源
28     shared_ptr<Children> s_children(new Children());  //调用拷贝对象构造 使s_children指向Children共享资源
29     if(s_parent && s_children)                        //两个对象成功创建的话
30     {
31         s_parent   -> s_children1 = s_children;       //2 使对象成员也指向Children共享资源
32         s_children -> s_parent1   = s_parent;         //使对象成员也指向Parent共享资源
33     }
34 
35     cout << "s_parent use_count: " << s_parent.use_count() << endl;     // 2
36     cout << "s_children use_count: " << s_children.use_count() << endl; // 2
37 }

  执行上面程序可以知道,析构并没有被调用,内存泄漏,解决方法是将成员变量其中一个改成weak_ptr,因为weak_ptr不占用引用数。以第一个变成weak_ptr为例,当两个shared对象释放时,成员对象s_children1不占用引用数,s_children(图下面那个)引用的count从1变成0,所以他可以调用析构函数析构掉它;而被析构时,他的成员变量s_parent1也会被析构,由于shared对象释放时减了1,所以count引用也变成了0,两个内存被释放,这样就解决了这个循环引用问题。

 

 

   构造执行了,且Children对象先被析构。因为s_children指向对象的引用先变为0先嘛。若改成第二个为weak,那么就s_parent指向的对象被析构,即Parent对象。

 

3.7.5 补充说明和使用建议

3.7.5.1 建议1

  移动语义是可以使用的,但是注意不要乱用,因为它所有权被夺走后相当于空。

 1 void test15(){
 2     shared_ptr<int> p1(new int(100));
 3     //移动语义,夺走p1内存所有权给一个新的智能指针p2管理,移动后p1就不能再管理该内存(变成空),引用计数依旧是1
 4     shared_ptr<int> p2(std::move(p1)); 
 5     //*p1 = 50;//报错
 6     if(p1 == nullptr){
 7         cout<<"p1被置空,count=" << p1.use_count() << endl;
 8     }
 9     if(p2 != nullptr){
10         cout<<"p2通过move夺走p1所有权, count=" << p2.use_count() << endl;
11     }
12  
13     shared_ptr<int> p3;
14     //移动赋值,p2指向空,p3指向该对象,整个对象的引用计数仍然为1
15     p3 = std::move(p2); 
16     if(p2 == nullptr){
17         cout<<"p2被置空,count=" << p2.use_count() << endl;
18     }
19     if(p3 != nullptr){
20         cout<<"p3通过move夺走p2所有权, count=" << p3.use_count() << endl;
21     }
22 }

结果

 

 

而当我们移动后再使用空的对象,则会报错,所以也要慎用。

 

3.7.5.2 建议2

    • 1)谨慎使用,奇怪用法不要轻易尝试。
    • 2)优先使用make_shared()。
      具体下面这两种的优势和劣势参考我这一篇-文章->智能指针之shared_ptr初始化,引用计数,常用操作和自定义删除器等等03。
1 shared_ptr<string> ps1(new string("good luck"));  //需要分配两次内存
2 auto ps2 = make_shared<string>("good luck");  //只分配一次

四、weak_ptr

4.1 简介

  看名字就知道,他是一个弱引用,他的存在是为了辅助shared_ptr解决循环引用问题,它不占用引用数。只能通过shared_ptr或者weak_ptr构造赋值。它没有重载 * 和 -> 运算符,因此不可以直接通过 weak_ptr 访问对象,典型的用法是通过 lock() 成员函数来获得 shared_ptr,进而使用对象。大家理解时,将weak_ptr看成用来监视shared_ptr(强引用)的生命周期用。是一种对shared_ptr的扩充。

4.2 weak_ptr初始化赋值

 1     //1 weak_ptr初始化赋值
 2     std::shared_ptr<int> sh = std::make_shared<int>();  // 默认值为0
 3     cout<<sh.use_count()<<endl;                         // 1
 4     std::weak_ptr<int> w(sh);                           // 用shared_ptr构造
 5     std::weak_ptr<int> w1(w);                           // 用weak_ptr构造
 6     cout<<sh.use_count()<<endl;                         // 1
 7 
 8     std::weak_ptr<int> w2=sh;                           // 用shared_ptr赋值
 9     std::weak_ptr<int> w3=w2;                           // 用weak_ptr赋值
10     cout<<sh.use_count()<<endl;                         // 1

4.3 lock()函数

  lock():检查weak_ptr所指向的对象是否存在,如果存在那么lock将返回一个指向该对象的shared_ptr(指向对象的强引用就会+1),如果指向对象不存在,lock会返回一个空的shared_ptr。通过lock可以将weak_ptr转化成shared_ptr使用。

4.4 expired()函数

  expired函数:判断weak_ptr的方法expired判断shared_ptr管理的资源是否已经释放。true已释放,0未释放

4.5 上面1,2,3大点的案例

 1 //4 weak_ptr  --小弟
 2 void test16(){
 3 
 4     //1 weak_ptr初始化赋值
 5     std::shared_ptr<int> sh = std::make_shared<int>();  // 默认值为0
 6     cout<<sh.use_count()<<endl;                         // 1
 7     std::weak_ptr<int> w(sh);                           // 用shared_ptr构造
 8     std::weak_ptr<int> w1(w);                           // 用weak_ptr构造
 9     cout<<sh.use_count()<<endl;                         // 1
10 
11     std::weak_ptr<int> w2=sh;                           // 用shared_ptr赋值
12     std::weak_ptr<int> w3=w2;                           // 用weak_ptr赋值
13     cout<<sh.use_count()<<endl;                         // 1
14 
15     /*
16         2. lock函数:检查weak_ptr所指向的对象是否存在,
17         如果存在那么lock将返回一个指向该对象的shared_ptr(指向对象的强引用就会+1),
18         如果指向对象不存在,lock会返回一个空的shared_ptr.
19         通过lock可以将weak_ptr转化成shared_ptr使用
20     */
21     std::shared_ptr<int> another = w2.lock();  
22     if (another != nullptr){
23         *another = 12;
24         cout<<"sh管理的内存未被释放"<<endl;
25     }
26     cout<<sh.use_count()<<endl;                         // 2
27 
28     //3 expired函数:判断weak_ptr的方法expired判断shared_ptr管理的资源是否已经释放。true已释放,0未释放
29     bool isDeleted = w2.expired();
30     cout<<sh.use_count()<<endl;                         // 2
31     cout<<isDeleted<<endl;                              // 0 因为程序还没结束 sh与another在引用 
32     
33     //测试lock为空时
34     another.reset();//无参reset置空,该内存引用数减1,本对象another的引用计数为0
35     sh.reset();
36     if (w2.lock() == nullptr){
37         cout<<"sh管理的内存被释放"<<endl;
38     }
39 }

结果

4.6 reset()函数

reset():将该引用的引用计数设置为空,不影响该片内存的引用计数,但是该片内存的引用计数会减少1。

4.7 尺寸问题

weak_ptr与shared_ptr的尺寸一样大,是裸指针的两倍。内部有两个裸指针

  • 1)第一个裸指针指向的是这个智能指针所指向的对象。
  • 2)第二个所指针指向一个数据结构(控制块),这个控制块里有:
    • 1、引用计数(强引用计数、弱引用计数)
    • 2、其他数据(弱引用删除器,互斥锁等等)

五、unique_ptr

5.1 unique_ptr概述

独占式的概念(所有权);同一时刻只能有一个unique_ptr指向这个对象(这块内存),当这个unique_ptr被销毁时,它所指向的对象也被销毁。

5.2 unique_ptr的初始化

5.2.1 正常初始化

1 unique_ptr<string> p1(new string("Hello World"));

5.2.2 C++14新特性make_unique函数初始化

  C++11中没有,C++14中才有make_unique,它不支持指定删除器的语法,如果不用删除器,建议优先选择使用,更高的性能。由于我的编译器最高支持C++11,所以没有这个函数。

1 unique_ptr<int> p1 = make_unique<int>(100);
2 auto p2 = make_unique<int>(200);

5.2.3 使用匿名对象初始化

 1 //方法1
 2 unique_ptr<string> p3=unique_ptr<string>(new string("Hello World"));
 3 //方法2(调用函数返回匿名对象)
 4 unique_ptr<string> return_unique_ptr(string str)
 5 {   
 6     //从函数返回一个局部的unique_ptr对象,这种局部对象,系统会给我们再次生 
 7     //成一个临时的unique_ptr对象,调用unique_ptr的移动构造函数
 8     unique_ptr<string> pr(new string(str));
 9     return pr;  
10 }
11 
12 unique_ptr<string> p4=return_unique_ptr("func return");

5.2.4 移动语义初始化

实际上移动语义和匿名对象初始化最终都是调用右值拷贝构造函数

1 unique_ptr<string> ps1(new string("good luck"));
2 unique_ptr<string> ps2 = std::move(ps1);

5.2.5 错误的使用拷贝构造,等号重载初始化

由于unique_ptr是没有实现拷贝构造(或者具体说它只支持右值拷贝构造,所以上面才能使用move构造)和等号重载的,所以不能使用拷贝构造和等号(赋值)重载初始化

1 unique_ptr<string> p1(new string("Hello World"));
2 unique_ptr<string> p2;
3 //unique_ptr<string> pp3(p1);     //报错,左值拷贝构造并未实现
4 //p2=p1;                          //报错 =重载函数只写了声明没写函数体

5.3 unique_ptr的成员函数和其它操作的使用

5.3.1 release()函数

  释放,调用后智能指针和其所指向对象的联系再无联系,但是该内存仍然存在有效。它会返回裸指针,但是该智能指针被置空
  返回的裸指针我们可以手工delete来释放,也可以用来初始化另外一个智能指针,或者给另外一个智能指针赋值。

1 unique_ptr<string> ps3(new string("good luck"));
2 unique_ptr<string> ps4(ps3.release());//此时ps4夺走ps3的使用权,ps3被置空。类似于移动语义,但区别是release返回裸指针,move返回本类型unique_ptr。
3 if(ps3==nullptr){
4     cout<<"ps3被释放掉"<<endl;
5 }

结果显示

5.3.2 reset()函数

reset()不带参数情况:释放智能指针所指向的对象(释放因为它是独占,而不像shared_ptr还需要考虑引用计数),并将智能指针置空
reset()带参数时:释放智能指针所指向的对象,并将该智能指针指向新对象

 1     //3 reset函数
 2     unique_ptr<string> ps5(new string("good luck"));
 3     unique_ptr<string> ps6(new string("good luck2"));
 4     /*
 5         首先ps6调用release后被置空,返回裸指针作为参数。然后
 6         由于ps5调用reset,ps5被置空,并指向新内存即ps6返回裸指针指向的那块内存
 7         所以我们打印ps5的数据应该是"good luck2"
 8     */
 9     ps5.reset(ps6.release());
10     if(ps5==nullptr){
11         cout<<"ps5被释放掉"<<endl;
12     }
13     if(ps6==nullptr){
14         cout<<"ps6被释放掉,内存被ps5使用"<<endl;
15     }
16     cout << ps5->data() << endl;

结果

5.3.3 nullptr的使用

实际上我们对于nullptr在上面已经使用过,用于比较。而这里也可以使用nullptr用于置空。所以nullptr目前在本篇可以作为

  • 1)比较。
  • 2)置空:释放智能指针所指向的对象,并将智能指针置空。
 1 class A {
 2 public:
 3     A() {};
 4     A(int i) {
 5         m_i=i;
 6         cout<<"A"<<endl;
 7     };
 8     void SetI(int i) {m_i=i;};
 9     int GetI(){return m_i;}
10     ~A() {
11         cout<<"~A"<<endl;
12     };
13 private:
14     int m_i;
15 };
16 
17 //4 nullptr的使用
18 unique_ptr<A> ps7(new A(522));
19 ps7 = nullptr; //释放ps7所指向的对象,并将ps7置空。
20 if(ps7 == nullptr){
21     cout<<"ps7被置空,并且所指内存也被释放"<<endl;
22 }

5.3.4 指向数组

1 //unique_ptr的自定义删除器
2 //unique_ptr<int,自定义删除器的类型> parr1(new int[5],自定义删除器);
3 unique_ptr<int[]> parr(new int[10]);
4 parr[0] = 12;
5 parr[1] = 12;

5.3.5 get()函数

get():返回智能指针中保存的裸指针。考虑到有些函数的参数需要内置的裸指针,所以引入该函数。

5.3.6 解引用(*)

解引用:获取该智能指针指向的对象,可以直接操作。

1 unique_ptr<string> ps8(new string("good"));
2 *ps8 = "good luck";

5.3.7 swap()函数

swap():交换两个智能指针所指向的对象。

1 unique_ptr<string> ps1(new string("good luck"));
2 unique_ptr<string> ps2(new string("good luck2"));
3 ps1.swap(ps2);
4 std::swap(ps1, ps2);//也可使用标准库的swap

5.4 如何将unique_ptr转换成shared_ptr类型

转换条件:如果unique_ptr为右值时,他就可以赋值给shared_ptr。
原因:因为shared_ptr包含一个显示构造函数可用于将右值unique_ptr转换为shared_ptr,shared_ptr将接管原来归unqiue_ptr所拥有的对象。

 1 auto myfunc()
 2 {
 3     return unique_ptr<string>(new string("good luck")); //这是一个右值(临时对象都是右值)
 4 }
 5  
 6 void test18()
 7 {
 8     shared_ptr<string> ps1 = myfunc(); //可以
 9     unique_ptr<int> pi1(new int(1));
10     shared_ptr<int> pi2 = std::move(pi1); //可以,移动后pi1被置空
11 
12     // unique_ptr<int> pi3(new int(100));
13     // shared_ptr<int> pi4(pi3);//不可以,原因pi4的参数不是右值
14 }

5.5 unique_ptr删除器

  unique_ptr和shared_ptr一样,默认删除器都是使用delete所以当我们创建的是一个数组或者文件这些时,显然delete是无法有效回收的删除器是一个可调用对象,其中可调用对象可以是普通函数,或者重载了()括号的类,或者lambda表达式等等unique_ptr删除器比shared_ptr多了一步,先要在类型模板参数中传递进去类型名,然后在参数中再给具体的其函数名

 1 void mydeleter(string *pdel)
 2 {
 3     delete pdel;
 4     pdel = nullptr;
 5     //可以打印日志
 6     cout<<"mydeleter"<<endl;
 7 }
 8  
 9 void test19(){
10 
11     // a.1
12     typedef void(*fp)(string *);
13     unique_ptr<string, fp> ps1(new string("good luck"), mydeleter);
14  
15     //a.2
16     using fp2 = void(*)(string *);
17     unique_ptr<string, fp2> ps2(new string("good luck"), mydeleter);
18  
19     //a.3
20     typedef decltype(mydeleter)* fp3; //decltype()返回函数类型void(string *),所以要加*,现在fp3应该是void *(string *)
21     unique_ptr<string, fp3> ps3(new string("good luck"), mydeleter);
22  
23     //a.4
24     unique_ptr<string, decltype(mydeleter)*> ps4(new string("good luck"), mydeleter);
25  
26     //a.5,lambda表达式,可以理解成operator()类类型对象
27     auto mydella = [](string *pdel)->void{
28         delete pdel;
29         pdel = nullptr;
30         cout<<"mydella"<<endl;
31     };
32     //注意,lambda表达式mydella被编译器理解成对象(即ps5的参2),所以我们类型不能加*,否则地址与对象不匹配
33     //而上面的函数名mydeleter代表是首地址,需要加*地址符号进行匹配。(对象不是对象,&对象才是地址)
34     unique_ptr<string, decltype(mydella)> ps5(new string("good luck"), mydella);
35  
36 }

结果

指定删除器额外说明

  • 1)shared_ptr:就算两个shared_ptr指定的删除器不同,只要他们指向的对象类型相同,那么他们属于同一个类型。可以放到同一个容器中。
  • 2)unique_ptr:指定unique_ptr中的删除器会影响unique_ptr类型。所以,从灵活性上来讲,shared_ptr更灵活。unique_ptr如果删除器不同,那么就等于整个unique_ptr类型不同,这种类型不同的unique_ptr智能指针是没法放到同一个容器中的

5.6 尺寸问题

当我们在上面代码添加求智能指针的所占字节数,如下:

//开头先求原指针所占字节数
string *str = NULL;
cout<<"未加删除器的指针所占字节数="<<sizeof(str)<<endl;

//然后在每个ps下面各自求出对应智能指针的所占字节数。这里只列出一个。
int ilen = sizeof(ps1);
cout << "ps1的所占字节数=" << ilen << endl;

结果:注意我的是32位下测试,所以原指针所占字节数为4。

小结:
  如果你增加了自己的删除器,则unique_ptr的尺寸可能增加,指定删除器后,尺寸发生变化。增加字节的对效率有影响。unique_ptr不管你指向什么删除器,unique_ptr的尺寸大小都是裸指针的2倍。但是不指定删除器的unique_ptr的大小和管理的裸指针的大小一样,weak_ptr和shared_ptr的大小是管理的裸指针的大小的两倍,因为它们的内部还有一个管理引用计数的结构。

六、智能指针的总结

目前开发我们只需要用到shared_ptr, unique_ptr已经足够,weak_ptr基本不需要使用,它更多时候是给那些开发标准库的人使用。
然后shared_ptr, unique_ptr的选择主要看下面的区别:

1)如果程序要是用多个指向同一个对象的指针,应选择shared_ptr。
2)如果程序不需要多个指向同一个对象的指针,应该首选unique_ptr。
讲到本篇,智能指针的所有知识点结束,我以前也总结过智能指针,但是不够全面,目前这8篇已经非常全面了,智能指针各种使用场合,和语法都包含进去,总的来说写这八篇文章还是有点难度的,原因是C++的新语法越来越看不懂,有点吃力。完结。

参考文章

https://blog.csdn.net/weixin_44517656/article/details/114167353

https://blog.csdn.net/weixin_44517656/article/details/114170328

https://blog.csdn.net/weixin_44517656/article/details/114171334

https://blog.csdn.net/weixin_44517656/article/details/114195265

https://blog.csdn.net/weixin_45590473/article/details/113101912?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_title~default-1.pc_relevant_baidujshouduan&spm=1001.2101.3001.4242

https://blog.csdn.net/weixin_44517656/article/details/114228284

https://blog.csdn.net/weixin_44517656/article/details/114208041

https://blog.csdn.net/weixin_44517656/article/details/114217244

posted @ 2021-07-31 11:02  Mr-xxx  阅读(376)  评论(0编辑  收藏  举报