智能指针

本文最后更新于:1 年前

针对于C++面试题,博主复习,C++11新标准的智能指针

1.概念

所谓的智能指针本质就是一个类模板,它可以创建任意的类型的指针对象,当智能指针对象使用完后,对象就会自动调用析构函数去释放该指针所指向的空间。

所有的智能指针类模板中都需要包含一个指针对象,构造函数和析构函数

2.三大智能指针

(1)unique_ptr

C++11版本库提供的智能指针,直接将拷贝构造函数和赋值重载函数给禁用掉,因此不让其进行拷贝和赋值。

1
2
3
unique_ptr<int> v1(new int(0));
unique_ptr<int> v2(v1); //Error 不能拷贝
v2 = v1; //Error 不能赋值重载

我们通过内存查看&v1,如下图:

&v1

我们找到了堆区内存地址,我们在内存中查看该地址,如下图:

堆区内存

地址中即为存储的0

unique_ptr它所指向的资源对象,该资源对象不会被多个unique_ptr对象同时占有,可以称之为独占指针

此外unique_ptr还有以下特性:

1)虽然unique_ptr对象之间不能进行拷贝和赋值,但是可以进行移动,如下:

1
2
3
4
//1.通过reset()
v1.reset(v2.release()); //v2.release() 将NULL赋值到指针对象中,并返回原先指针对象关联的资源对象的裸指针
//2.通过move
v1.move(v2);//令v2为NULL,将v2的原始指针传给v1

Tips: get()和release()的区别:

  • get()是返回一个裸指针但原始指针不变,unique_ptr可以和普通指针共同管理一个对象,并且普通对象的操作可以反映到unique_ptr指针所指向的对象上

    就是使用v1.get()和直接使用v1是一样的效果!

  • 而release()是返回一个裸指针并将原始指针变为NULL

个人还觉得,尽量避免智能指针和普通指针的混合,那样容易非常的乱,导致内存泄露,导致崩溃

  1. unique_ptr 没有重载加减运算符 但是可以通过普通指针来进行操作
1
2
3
unique_ptr<int> v3(new int);
int*v4 = v3.get();
(*v4)++; //注意只有对所指向的对象进行操作时才会反映到unique_ptr所指向的对象上

同时我们使用get()函数获取原始指针后,最后不需要delete,因为和智能指针指向同一片堆区内存,智能指针脱离生命周期后会自动析构,所以普通指针也会被销毁掉,因此不需要执行销毁操作。

更多

  1. 使用unique_ptr实现虚拟多态
1
2
3
4
unique_ptr<MyClass1> v1 = NULL;  //父类对象指针
unique_ptr<MyClass2> v2(new MyClass2); //子类对象指针,指向一片堆区内存
v1.reset(v2.release()); //将子类的对象指针返回给父类,子类为NULL
v1->Sub_1(); //父类调用子类成员函数
  1. 自定义删除器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1.函数指针方式
void Sub_6(int* ParameterData)
{
delete ParameterData;
}
auto __xx = [](int* ParameterData){
_tprintf(_T("Sub_6\r\n"));
delete ParameterData;};//匿名函数 只占资源对象地址 lambda表达式
2.void Sub_5()
{
unique_ptr<int,void(*)(int*)>v1(new int,Sub_6); //函数指针 资源对象地址+删除器函数指针地址
unique_ptr<int,decltype(Sub_6)*>v1(new int,Sub_6);
//以上两种表达方式相同
unique_ptr<int,decltype(__xx)>v1(new int,__xx);
}

(2)shared_ptr

结构:有两个成员,第一成员是原始指针,为从堆区申请的动态内存空间的地址

第二成员是用于引用计数的内存对象,是由_Ref_count_base类继承而来的 _Ref_count类创建的对象

该对象有四个成员:

第一个成员为虚表地址:因为是派生类的对象,调用了父类的虚函数

第二个成员为**__Users**:

第三个成员为**__Weaks**

第四个成员为原始指针

其构造函数和unique_ptr不同点在于,其有一个计数器

而且,shared_ptr可以进行拷贝赋值

每进行一次拷贝赋值**__Users就需要++**操作

并且在拷贝构造后,v2v1会指向同一片内存

1
2
3
4
5
shared_ptr<int> v1(new int(0));  //构造函数
shared_ptr<int> v2(v1); //拷贝构造函数
shared_ptr<int> v3 = v1; //拷贝构造函数
shared_ptr<int> v4;
v4 = v1; //等号运算符重载

不能使用原始指针对shared_ptr初始化
错误例子:

1
2
3
shared_ptr<int> v5 = make_shared<int>(10);
shared_ptr<int> v6(v5.get());
shared_ptr<int> v7(v5.get());

自定义删除器:

1
2
shared_ptr<int> v1(new int[10],default_delete<int[]>()); //自定义指针数组删除器
v1.reset(); //会使得v2的Users--,因为v1为空了,相当于v2共享资源的对象少了一个 可以通过该手段进行资源回收

使用shared_ptr传参时,也会影响Users++ 相当于又创建了一个对对象的引用指针
新标准支持使用中括号对智能指针数组进行访问
返回值类型为shared_ptr时,也会造成**_Users++** ,前提是,函数内部中的shared_ptr是静态的(生命周期全局,引用数不–),如果非静态,当离开当前作用域后就会自动调用析构函数(引用数–)

(2)weak_ptr

辅助shared_ptr使用
解决循环引用的问题
//shared_ptr赋予一个新值
//shared_ptr为局部变量离开了作用域,或是shared_ptr被销毁

解决方案:

将其中一个类成员shared_ptr指针更改为weak_ptr
weak_ptr与shared_ptr结构一致
不能单独被new出来,只能辅助shared_ptr
与shared_ptr指向同一片内存时,其内部的_Users不会++

1
2
3
4
5
6
7
8
9
shared_ptr<MyClass1> v1(new MyClass1());
shared_ptr<MyClass2> v2(new MyClass2());
v1->m_1 = v2;
v2->m_1 = v1;
-------------
//MyClass1
weak_ptr<MyClass1>m_1(new int);
//MyClass2
shared_ptr<MyClass2>m_1(new int);

1.当MyClass1和MyClass2中都有一个shared_ptr成员m_1时,整个程序结束后,v1和v2的引用计数都是1,没有为0,产生内存泄漏。

2.当有一个为weak_ptr时,假设是Myclass1中m_1为weak_ptr,这时 v1 -> m_1 = v2 后,v2只有一次引用计数,v2析构后,其为0。当v1析构后,此时MyClass2中的m_1消失,v1引用计数变为1,然后,因为v1析构,其引用计数再–,变为0


智能指针
https://wlpswmt.github.io/2023/03/17/智能指针/
作者
Sivan Zhang
发布于
2023年3月17日
许可协议