一、智能指针
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