C++智能指针
C++智能指针
为什么要使用智能指针?
<<C++ Primer>> p400
虽然使用动态内存有时是必要的,但众所周知,正确地管理动态内存是非常棘手的。
为了更容易(同时也更安全的)地使用动态内存,新的标准库提供了两种智能指针,来管理动态对象。智能指针的行为类似于常规指针,重要的区别是它负责自动释放所指向的对象。
shared_ptr允许多个指针指向同一个对象,unique_ptr是“独占”所指向的对象。标准库还定义了一个名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。这三种类型都定义在memeory头文件中。
原理:
将我们分配的动态内存都交给有生命周期的对象来处理,当对象过期时,让它的析构函数删除指向的内存。
- C++98 提供了 auto_ptr模板的解决方案
- C++11 增加了 unique_ptr、shared_ptr、weak_ptr
(就是一个类模板,里面有析构函数,能够自动释放这个对象开辟的内存。)
auto_ptr
C++98的智能指针模板,其定义了管理指针的对象,可以将new获得(直接或间接获得)的地址赋值给这种对象。当对象过期时,其析构函数会用delete来释放内存。
就是一个类模板,自动调用析构函数释放。
用法
1 |
|
1 |
|
方法:
1 | test.get();//得到new出来的指针,一般不会这么用 |
补充——new 一个对象加不加括号-链接
建议
1.尽可能不要将auto_ptr 变量定义为全局变量或指针,程序结束之后释放,没有意义。
2.不要定义指向智能指针的指针。不会自动释放指针的指针。
3.除非自己知道后果,不要把auto_ptr 智能指针赋值给同类型的另外一个智能指针,解释如下。
4.C++11 后auto_ptr 已经被“抛弃”,已使用unique_ptr替代!
unique_ptr
auto_ptr弊端
auto_ptr是用于C++11之前的智能指针。由于auto_ptr基于排他所有权模式,两个之怎不能指向同一个资源,复制或赋值都会改变资源的所有权。auto_ptr主要问题如下:
- 复制和赋值会改变资源的所有权,不符合人的直觉。
- 在 STL 容器中使用auto_ptr存在重大风险,因为容器内的元素必需支持可复制(copy constructable)和可赋值(assignable)。
- 不支持对象数组的操作。
1 | 弊端1. auto_ptr 被C++11 抛弃的主要理由 p1= p2 ,复制或赋值都会改变资源的所有权 |
1 | 智能指针的内存管理陷阱(不只是它的缺陷,unique_ptr也有) |
1 | 弊端2.在 STL 容器中使用auto_ptr存在重大风险 |
1 | 弊端3.不支持对象数组的内存管理 |
总上所述,C++11用了更严谨的unique_ptr取代了aoto_ptr;
特性
- 基于排他所有权模式:两个指针不能指向同一个资源。
- 无法进行左值unique_ptr复制构造,也无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值。std::move。把右值转换为左值。
- 保存指向某个对象的指针,当它本身离开作用域时会自动释放它指向的对象。
- 在容器中保存指针是安全的。不支持直接复制v[0] = v[1]不行。
- 支持对象数组的内存管理,自动调用delete释放。
构造函数
1 | unique_ptr<T> up ; //空的unique_ptr,可以指向类型为T的对象 |
删除器
利用一个仿函数实现一个删除器
1 | class DestructTest |
赋值
(接管所有权)一定要使用移动语义
(可以对比理解一下类中的深浅拷贝)
1 | unique_ptr<int> s1(new int(1)); |
主动释放对象
1 | unique_prt<int> s3(new int(3)); |
放弃对象控制权
1 | s3.release();//放弃对象的控制权,返回指针,然后将s3重为空 |
**<<C++ Primer>>**p418
调用release会切断unique_prt和它原来管理对象间的联系。release返回的指针通常被用来初始化另一个智能指针或给另一个智能指针赋值。在本例中,管理内存的责任简单地从一个指针转给了另一个。但是如果我们不用另一个智能指针来保存release返回的指针,我们的程序就要负责资源的释放。
1 | p2.release();//错误,p2不会释放内存,而且我们丢失了指针。 |
交换
1 | s3.swap(s4);//将智能指针s3和s4所管控的对象进行交换。 |
重置
1 | s3.reset();//参数可以为空、内置指针,先将up所指向的对象释放,然后重置up的值,将up指向新的玩意儿。放一个地址进去指向这个地址对应的东西。 |
(指针指向的是变量,存的是该变量的地址。)
share_ptr
熟悉了unique_ptr 后,其实我们发现unique_ptr 这种排他型的内存管理并不能适应所有情况,有很大的局限!如果需要多个指针变量共享怎么办?
如果有一种方式,可以记录引用特定内存对象的智能指针数量,当复制或拷贝时,引用计数加1,当智能指针析构时,引用计数减1,如果计数为零,代表已经没有指针指向这块内存,那么我们就释放它!这就是 shared_ptr 采用的策略!
构造函数
1 | shared_ptr<T> sp ; //空的shared_ptr,可以指向类型为T的对象 |
数组对象的管理:
1 | shared_ptr<Person[]>sp1(new Person[5](3,4,5,6,7)); |
初始化
方式1:构造函数
1 | shared_ptrr<int> up1(new int(10)); //int(10) 的引用计数为1 |
方式2:使用make_shared初始化对象,分配内存效率更高
make_shared函数的主要功能是在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr;
(make_shared不算引用计数)
1 | 用法: |
赋值
1 | shared_ptrr<int> up1(new int(10)); //int(10) 的引用计数为1 |
主动释放对象
1 | shared_prt<int>up(new int(10)); |
重置
1 | up.reset(); //将up重置为空指针,所管理对象引用计数 减1 |
交换
1 | std::swap(p1,p2); //交换p1 和p2 管理的对象,原对象的引用计数不变 |
使用陷阱
shared_ptr作为被管控的对象的成员时,小心因循环引用造成无法释放资源。
1 |
|
断开其中的一条链接之后,两个指针即可正常的释放
weak_ptr
为了解决shared_ptr交叉循环引用无法释放的问题。
weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少. 同时weak_ptr 没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr 对象。
为了解决上面shared_ptr所出现的问题。提出weak_ptr,不影响引用计数。
在必要的时候可以转换成shared_ptr
.lock();
完美解决。
类中弱指针,用shared指针构造weak指针,用的时候,将weak指针转成shared指针来调用成员函数。
1 |
|
智能指针注意要点
- 不要把一个原生指针给多个智能指针管理
1 | int *x = new int(10); |
- 记得使用u.release()的返回值
1 | 在调用u.release()时是不会释放u所指的内存的,这时返回值就是对这块内存的唯一索引,如果没有使用这个返回值释放内存或是保存起来,这块内存就泄漏了 |
- 禁止delete 智能指针get 函数返回的指针
1 | 如果我们主动释放掉get 函数获得的指针,那么智能 指针内部的指针就变成野指针了,析构时造成重复释放,带来严重后果! |
- 禁止用任何类型智能指针get 函数返回的指针去初始化另外一个智能指针!
1 | shared_ptr<int> sp1(new int(10)); |