动态内存与智能指针
C++程序运行时,将内存分为四个区域,即:代码区、全局区、栈区、堆区。
其中堆区(heap)的一般使用一对运算符 new
和 delete
来管理。为了更方便的管理动态内存,C++提供了两种智能指针 shared_ptr
和 unique_ptr
。智能指针与普通指针用法类似,但它可以自动释放所指向的对象。标准库还定义了一种 weak_ptr
的伴随类,这三种类型都在<memory
头文件中。其中 shared_ptr
允许多个指针指向同一个对象,unique_ptr
则独占所指向的对象,weak_ptr
是一种弱引用,指向 shared_ptr
所管理的对象。
动态内存使用场景:
- 程序不知道自己需要使用多少对象
- 程序不知道所需对象的准确类型
- 程序需要在多个对象间共享数据
1 shared_ptr类
1.1 定义和初始化
// 默认初始化
shared_ptr<string> p1; // 需给出指向的类型,p1中保存着一个空指针
// 使用make_shared函数初始化,会在动态内存中分配一个对象并初始化它
shared_ptr<int> p2 = make_shared<int>(42); // 指向一个值为42的int型变量
// 指向一个值为“9999999999”的string
shared_ptr<string> p3 = make_shared<string>(10, '9');
// 指向一个值初始化的int型变量,值为0
shared_ptr<int> p4 = make_shared<int>();
// 可以使用auto保存make_shared的结果
auto p5 = make_shared<vector<string>>();
// 使用new返回的指针进行初始化
// 注意!接受指针参数的智能指针构造函数是explicit的。因此,不能将一个内置指针隐式转换为一个智能指
// 针,必须使用直接初始化形式来初始化一个智能指针!
shared_ptr<int> p1 = new int(1024); // 错误!无法将 int* 类型转换为 shared_ptr 类型
shared_ptr<int> p2(new int(1024)); // 正确!直接初始化形式
1.2 拷贝和赋值
当进行拷贝或赋值操作时,每个 shared_ptr 都会记录有多少个其他 shared_ptr 指向相同的对象,通常称其为引用计数(reference count)。在拷贝一个 shared_ptr 时计数器会递增,给 shared_ptr 赋新值或 shared_ptr 被销毁时计数器会递减。计数器变为0就会自动释放所管理的对象。
auto p1 = make_shared<int>(); // p1所指向的int的引用计数为1
auto p2(p1); // p2与p1指向同一个int,引用计数为2
auto q = make_shared<int>(42); // q指向一个值为42的int
q = p2; // q指向p1、p2所指向的int,引用计数为3
// q原来所指向的值为42的int引用计数为0,被自动释放
1.3 其他操作
2 unique_ptr
2.1 定义和初始化
与shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时,它所指向的对象也被销毁。
unique_ptr<double> p1; // 可以指向一个double的unique_ptr
unique_ptr<int> p2(new int(666)); // 指向一个值为666的int
2.2 其他操作
一个unique_ptr拥有它指向的对象,因此unique_ptr不支持普通的拷贝或赋值操作。
unique_ptr<int> p3(new int()); // 正确!
unique_ptr<int> p4 = p3; // 错误!unique_ptr不支持赋值操作
unique_ptr<int> p5(p3); // 错误!unique_ptr不支持拷贝操作
虽然不能拷贝或赋值unique_ptr,但可以通过调用release或reset将指针的所有权从一个(非const)unique_ptr转移给另一个unique。注意:调用release会切断unique_ptr和它原来管理的对象间的联系,但并不会释放内存。release返回的指针通常被用来初始化另一个智能指针或给另一个智能指针赋值。若没有用另一个智能指针来保存release释放的指针,则用户必须手动释放该部分内存。
不能拷贝unique_ptr的规则有一个例外:可以拷贝或赋值一个将要被销毁的unique_ptr。
// 下面两种情况,编译器都知道要返回的对象将要被销毁。在此情况下,编译器执行一种特殊的“拷贝”。
unique_ptr<int> clone(int num) {
return unique_ptr<int>(new int(num));
}
unique_ptr<int> clone2(int num) {
unique_ptr<int> tmp(new int(num));
return tmp;
}
3 weak_ptr
3.1 定义和初始化
weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放。
当我们创建一个weak_ptr时,要用一个shared_ptr来初始化它:
auto p1 = make_shared<int>(666);
weak_ptr<int> wp2(p1);
3.2 使用方式
由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用lock。此函数检查weak_ptr指向的对象是否仍存在。如果存在,lock返回一个指向共享对象的shared_ptr。
if (shared_ptr<int> np = wp2.lock()) {
// 只有当lock调用返回true时才会进入if语句体。在if中,使用wp2访问共享对象是安全的
}