C++智能指针unique_ptr详解
程序可能会因此出现悬挂指针,也就是说,指针已经被删除了,但其内存仍然在使用中;还可能出现内存泄漏,也就是说,即使已经不再需要内存了,但指针仍然未被删除。另外还有双重删除的问题,当程序的某 部分要删除一个已经被删除的指针时,即可出现这种情况。如果被删除的内存已经进行了重新分配,则双重删除会对程序造成破坏。
C++ 11 引入了智能指针的概念来解决该问题。智能指针是一个可以像指针一样工作的对象,但是当它不再被使用时,可以自动删除动态分配的内存。
C++11 提供了 3 种智能指针类型,它们分别由 unique_ptr 类、shared_ptr 类和 weak_ptr 类定义,所以又分别称它们为独占指针、共享指针和弱指针。
智能指针背后的核心概念是动态分配内存的所有权。智能指针被称为可以拥有或管理它所指向的对象。当需要让单个指针拥有动态分配的对象时,可以使用独占指针。对象的所有权可以从一个独占指针转移到另一个指针,其转移方式为:对象始终只能有一个指针作为其所有者。当独占指针离开其作用域或将要拥有不同的对象时,它会自动释放自己所管理的对象。
共享指针将记录有多少个指针共同享有某个对象的所有权。当有更多指针被设置为指向该对象时,引用计数随之增加;当指针和对象分离时,则引用计数也相应减少。当引用计数降低至0时,该对象被删除。
unique_ ptr、shared_ ptr 和 weak_ ptr 类是在 memory 头文件中定义的,所以需要在使用它们的程序中包含以下语句:
#include <memory>
本节我们先来讨论 unique_ ptr。智能指针实际上是一个对象,在对象的外面包围了一个拥有该对象的普通指针。这个包围的常规指针称为裸指针。
智能指针类可以通过它所指向的对象类型设置形参。例如,unique_ptr<int> 就是一个指向 int 的指针;而 unique_ptr<double> 就是一个指向 double 的指针。以下代码显示了如何创建独占指针:
unique_ptr<int> uptr1(new int);
unique_ptr<double> uptr2(new double);
unique_ptr<int> uptr3;
uptr3 = unique_ptr<int> (new int);
例如,应该避免按以下方式编写代码:
int *p = new int;
unique_ptr<int> uptr(p);
uptr1 ++;
uptr1 = uptr1 + 2;
*
和 ->
。以下代码将解引用一个独占指针,以给动态分配内存位置赋值,递增该位置的值,然后打印结果:
unique_ptr<int> uptr(new int);
*uptr = 12;
*uptr = *uptr + 1;
cout << *uptr << endl;
unique_ptr<int> uptr1(new int);
unique_ptr<int> uptr2 = uptr1; // 非法初始化
unique_ptr<int> uptr3; // 正确
uptr3 = uptr1; // 非法赋值
unique_ptr<int> uptr1(new int);
*uptr1 = 15;
unique_ptr<int> uptr3; // 正确
uptr3 = move (uptr1) ; // 将所有权从 uptr1 转移到 uptr3
cout << *uptr3 << endl; // 打印 15
U = move(V);
那么,当执行该语句时,会发生两件事情。首先,当前 U 所拥有的任何对象都将被删除;其次,指针 V 放弃了原有的对象所有权,被置为空,而 U 则获得转移的所有权,继续控制之前由 V 所拥有的对象。不能直接通过值给函数传递一个智能指针,因为通过值传递将导致复制真正的形参。如果要让函数通过值接收一个独占指针,则在调用函数时,必须对真正的形参使用 move() 函数:
//函数使用通过值传递的形参 void fun(unique_ptr<int> uptrParam) { cout << *uptrParam << endl; } int main() { unique_ptr<int> uptr(new int); *uptr = 10; fun (move (uptr)); // 在调用中使用 move }以上代码将打印来自于函数 fun() 中的 10。
当然,如果通过引用传递的方式,那就不必对真正的形参使用 move() 函数了。示例代码如下:
//函数使用通过引用传递的值 void fun(unique_ptr<int>& uptrParam) { cout << *uptrParam << endl; } int main() { unique_ptr<int> uptr(new int); *uptr1 = 15; fun (uptr1) ; //在调用中无须使用move }以上代码在执行时将打印数字 15。
有趣的是,可以从函数中返回一个独占指针,这是因为在遇到返回 unique_ptr 对象的函数时,编译器会自动应用 move() 操作以返回其值。来看以下代码:
//返回指向动态分配资源的独占指针 unique_ptr<int> makeResource() { unique_ptr<int> uptrResult(new int); *uptrResult = 55; return uptrResult; } int main() { unique_ptr<int> uptr; uptr = makeResource () ; // 自动移动 cout << *uptr << endl; }该程序的输出结果为 55。
永远不要试图去动态分配一个智能指针,相反,应该像声明函数的局部变量那样去声明智能指针。当 unique_ptr 将要离开作用域时,它管理的对象也将被删除。如果要删除智能指针管理的对象,但同时又保留智能指针在作用域中,则可以将其值设置为 nullptr,或者调用其 reset() 成员函数,示例如下:
uptr = nullptr;
uptr.reset();
unique_ptr<int> uptr(new int);
现在可以弃用上面的代码,而改为使用以下代码:unique_ptr<int> uptr = make_unique<int>();
指向数组的独占指针
按上述方式创建的独占指针将对指向已删除的被管理对象的包围指针调用 delete,但是,如果该包围指针指向的是一个对象数组的话,那么这种操作就是不正确的。要确保调用 delete[] 来处理被解除分配的对象数组,则应该在对象类型后面包含一对空的方括号 []。例如,要使用指向动态分配 5 个整数数组的独占指针,需编写以下语句:
unique_ptr<int[]> uptr(new int[5]);
前面介绍过,智能指针 uptr 可以像一个指向int的普通指针那样使用;前面还介绍过,可以对指针使用数组符号,因此,可以如以下方式编写一个程序,在像 up[k] 这样的数组中存储整数的平方值:int main() { //指向数组的独占指针 uriique_ptr<int [ ] > up (new int [5]); //设置数组元素为整数的平方值 for (int k = 0; k < 5; k++) { up[k] = (k + l)*(k + 1); } //打印数组元素 for (int k = 0; k < 5; k++) { cout << up[k] <<" "; } cout << endl; }以上代码的输出结果将是
"1 4 9 16 25"
。当用于创建指向 T 类型对象数组的独占指针时,make_unique<T []>() 将釆用整数形参作为数组的大小:
unique_ptr<int[]> up = make_unique<int[]>(5);
unique_ptr 类的成员函数
unique_ptr 类有一些非常有用的实例成员函数,如表 1 所示。成员函数 | 描 述 |
---|---|
reset() | 销毁由该智能指针管理的任何可能存在的对象。该智能指针被置为空 |
reset(T* ptr) | 销毁由该智能指针当前管理的任何可能存在的对象。该智能指针继续控制由裸指针 ptr 指向的对象 |
get() | 返回该智能指针管理的由裸指针指向的对象。如果某个指针需要传递给函数,但是 该函数并不知道该如何操作智能指针,则 get() 函数非常有用 |
所有教程
- C语言入门
- C语言编译器
- C语言项目案例
- 数据结构
- C++
- STL
- C++11
- socket
- GCC
- GDB
- Makefile
- OpenCV
- Qt教程
- Unity 3D
- UE4
- 游戏引擎
- Python
- Python并发编程
- TensorFlow
- Django
- NumPy
- Linux
- Shell
- Java教程
- 设计模式
- Java Swing
- Servlet
- JSP教程
- Struts2
- Maven
- Spring
- Spring MVC
- Spring Boot
- Spring Cloud
- Hibernate
- Mybatis
- MySQL教程
- MySQL函数
- NoSQL
- Redis
- MongoDB
- HBase
- Go语言
- C#
- MATLAB
- JavaScript
- Bootstrap
- HTML
- CSS教程
- PHP
- 汇编语言
- TCP/IP
- vi命令
- Android教程
- 区块链
- Docker
- 大数据
- 云计算