12.1.3 shared_ptr 和 new 结合使用
shared_ptr<double> p1; // shared_ptr 指向一个 double
shared_ptr<int> p2(new int(42)); // p2 指向一个值为 42 的 int
- 定义和改变 shared_ptr 的其他方法
shared_ptr<T> p(q) // p 管理内置指针 q 所指向的对象; q 必须指向 new 分配的内存,且能够转换为 T*类型
shared_ptr<T> p(u) // p 从 unique_ptr u 那里接管了对象的所有权,将 u 置为空
p.reset() // 若 p 是唯一指向其对象的 shared_ptr , reset 会释放此对象
p.reset(q) // 若传递了可选的参数内置指针 q ,会令 p 指向 q,否则会将 p 置为空
- 不要使用 get 初始化另一个智能指针或为智能指针赋值
智能指针类型定义了一个名为 get 的函数,它返回一个内置指针,指向智能指针管理的对象。此函数是为了这样一种情况而设计的: 我们需要向不能使用智能指针的代码传递一个内置指针。使用 get 返回的指针的代码不能 delete 此指针
get 用来将指针的访问权限传递给代码,你只有在确认代码不会 delete 指针的情况下,才能使用 get 。特别是,永远不要用 get 初始化另一个智能指针或者为另一个智能指针赋值
12.1.4 智能指针和异常
- 如果使用智能指针,即使程序块过早结束,智能指针类也能确保在内存不再需要时将其释放
- 如果使用内置指针管理内存,且在 new 之后在对应的 delete 之前发生了异常,则内存不会被释放
- 智能指针的陷阱
1、不使用相同的内置指针值初始化 (或 reset ) 多个智能指针
2、不 delete get() 返回的指针
3、不使用 get() 初始化或 reset 另一个智能指针
4、如果你使用 get() 返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了
5、如果你使用智能指针管理的资源不是 new 分配的内存,记住传递给它一个删除器
12.1.5 unique_ptr
- 一个 unique_ptr “拥有”它指向的对象。与 shared_ptr 不同,某个时刻只能有一个 unique_ptr 指向给定的对象。当 unique_ptr 被销毁时,它所指向的对象也被销毁
- 与 shared_ptr 不同,没有类似 make_shared 的标准库函数返回一个 unique_ptr 。当我们定义一个 unique_ptr 时,需要将其绑定到一个 new 返回的指针上,类似 shared_ptr ,初始化 unique_ptr 必须采用直接初始化形式
unique_ptr<double> p1; // 可以指向一个 double 的 unique_ptr
unique_ptr<int> p2(new int(42)); // p2 指向一个值为 42 的 int
- 由于一个 unique_ptr 拥有它指向的对象,因此 unique_ptr 不支持普通的拷贝或赋值操作
unique_ptr<string> p1(new string("Stegosaurus"));
unique_ptr<string> p2(p1); // 错误:unique_ptr 不支持拷贝
unique_ptr<string> p3;
p3 = p2; // 错误:unique_ptr 不支持赋值
- unique_ptr 操作
unique_ptr<T> u1 // 空 unique_ptr ,可以指向类型为 T 的对象。u1 会使用 delete
unique_ptr<T, D> u2 // u2 会使用一个类型为 D 的可调用对象来释放它的指针
u = nullptr // 释放 u 指向的对象,将 u 置为空
u.release() // u 放弃对指针的控制权,返回指针,并将 u 置为空
u.reset() // 释放 u 指向的对象
u.reset(q) // 如果提供了内置指针 q ,令 u 指向这个对象;否则将 u 置为空
u.reset(nullptr)
- 虽然我们不能拷贝或赋值 unique_ptr,但可以通过调用 release 或 reset 将指针的所有权从一个 unique_ptr 转移到另一个 unique_ptr
// 将所有权从 p1 (指向 string Stegosaurus ) 转移给 p2
unique_ptr<string> p2(p1.release()); // release 将 p1 置为空
unique_ptr<string> p3(new string("Trex"));
// 将所有权从 p3 转移给 p2
p2.reset(p3.release()); // reset 释放了 p2 原来指向内存
release 成员返回 unique_ptr 当前保存的指针并将其置为空。因此,p2 被初始化为 p1 原来保存的指针,而 p1 被置为空
不能拷贝 unique_ptr 的规则有一个例外:我们可以拷贝或赋值一个将要被销毁的 unique_ptr<string>。最常见的例子是从函数返回一个 unique_ptr<string>
unique_ptr<int> clone(int p){
// 正确:从 int* 创建一个 unique_ptr<int>
return unique_ptr<int>(new int(p));
}
// 还可以返回一个局部对象的拷贝
unique_ptr<int> clone(int p){
unique_ptr<int> ret(new int(p))
// ...
return ret;
}
对于两段代码,编译器都知道要返回的对象将要被销毁。在此情况下,编译器执行一种特殊的 "拷贝"
12.1.6 weak_ptr
- weak_ptr 是一种不控制所指向对象生存期的智能指针,它指向由一个 shared_ptr 管理的对象
- 将一个 weak_ptr 绑定到一个 shared_ptr 不会改变 shared_ptr 的引用计数
- 一旦最后一个指向对象的 shared_ptr 被销毁,对象就会被释放。即使有 weak_ptr 指向对象,对象也还是会被释放
- weak_ptr 的名字抓住了这种智能指针 "弱" 共享对象的特点
- weak_ptr 操作
weak_ptr<T> w // 空 weak_ptr 可以指向类型为 T 的对象
weak_ptr<T> w(sp) // 与 shared_ptr sp 指向相同对象的 weak_ptr。T 必须能转换为 sp 指向的类型
w = p // p 可以是一个 shared_ptr 或一个 weak_ptr 。赋值后 w 与 p 共享对象
w.reset() // 将 w 置为空
w.use_count() // 与 w 共享对象的 shared_ptr 的数量
w.expired() // 若 w.use_count() 为 0,返回 true,否则返回 false
w.lock() // 如果 expired 为 true,返回一个空 shared_ptr ;否则返回一个指向 w 的对象的 shared_ptr
- 当我们创建一个 weak_ptr 时,要用一个 shared_ptr 来初始化它
本例子中 wp 和 p 指向相同的对象。由于是弱共享,创建 wp 不会改变 p 的引用计数;wp 指向的对象可能会被释放掉
auto p = make_shared<int>(42);
weak_ptr<int> wp(p); // wp 弱共享 p ; p 的引用计数未改变
- 由于对象可能不存在,我们不能使用 weak_ptr 直接访问对象,而必须调用 lock。此函数检查 weak_ptr 指向的对象是否存在。如果存在,lock 返回一个指向共享对象的 shared_ptr 。与任何其他 shared_ptr 类似,只要此 shared_ptr 存在,它指向的底层对象也就会一直存在。例如
在这段代码中,只有当 lock 调用返回 true 时我们才会进入 if 语句体
在 if 中,使用 np 访问共享对象是安全的
if(shared_ptr<int> np = wp.lock()){ // 如果 np 不为空则条件成立
// 在 if 中,np 和 p 共享对象
}
#include <iostream>
using namespace std;
#include<memory>
class AA{
public:
AA(int oo): kk(oo) {
cout << kk << " 构造函数" << endl;
}
~AA(){
cout << kk << " 析构函数" << endl;
}
private:
int kk;
};
int main(){
shared_ptr<AA> a(new AA(99));
shared_ptr<AA> b = make_shared<AA>(88);
return 0;
}
99 构造函数
88 构造函数
88 析构函数
99 析构函数
12.2 动态数组
12.2.1 new 和数组
为了让 new 分配一个对象数组,我们要在类型名之后跟一对方括号,在其中指明要分配的对象的数目。返回值是指向第一个对象的指针
int *pia = new int[10]; // 10 个未初始化的 int
int *pia2 = new int[10](); // 10 个值初始化为 0 的 int
string *psa = new string[10]; // 10 个空 string
string *psa2 = new string[10](); // 10 个空string
在新标准中,我们还可以提供一个元素初始化器的花括号列表
// 10 个 int 分别用列表中对应的初始化器初始化
int *pia3 = new int[10]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
// 10 个 string,前 4 个用给定的初始化器初始化,剩余的进行值初始化
string *psa3 = new string[10]{"a", "an", "the", string(3, 'x')};
- 释放动态数组
delete p; // p 必须指向一个动态分配的对象或为空
delete [] pa; // pa 必须指向一个动态分配的对象或为空
第二条语句销毁 pa 指向的数组中的元素,并释放对应的内存。
数组中的元素按逆序销毁,即,最后一个元素首先被销毁,然后是倒数第二个,以此类推
如果我们在 delete 一个数组指针时忘记了方括号,或者在 delete 一个单一对象的的指针时使用了方括号,
编译器很可能不会给出警告。我们的程序可能在执行过程中在没有任何警告的情况下行为异常
- 智能指针和动态数组
标准库提供了一个可以管理 new 分配的数组的 unique_ptr 版本。
为了用一个 unique_ptr 管理动态数组,我们必须在对象类型后面跟一对空方括号
// up 指向一个包含 10 个未初始化 int 的数组
unique_ptr<int[]> up(new int[10]);
up.release(); // 自动用 delete [] 销毁其指针
可以用下标来访问数组中的元素
for(int i=0; i != 10; i++){
up[i] = i; // 为每个元素赋予一个新值
}
// 指向数组的 unique_ptr 操作
指向数组的 unique_ptr 不支持成员访问运算符 (点和箭头运算符)
unique_ptr<T[]> u // u 可以指向一个动态分配的数组,数组元素类型为 T
unique_ptr<T[]> u(p) // u 指向内置指针 p 所指向的动态分配的数组。p 必须能转换为类型T*
u[i] // 返回 u 拥有的数组中位置 i 处的对象,u 必须指向一个数组
与 unique_ptr 不同,shared_ptr 不直接支持管理动态数组
如果希望使用 shared_ptr 管理一个动态数组,必须提供自己定义的删除器
// 为了使用 shared_ptr ,必须提供自己定义的删除器
shared_ptr<int> sp(new int[10], [] (int *p) {delete [] p;});
sp.reset(); // 使用我们提供的 lambda 释放数组,它使用 delete []
shared_ptr 未定义下标运算符,而且智能指针类型不支持指针算术运算
因此,为了访问数组中的元素,必须用 get 获取一个内置指针,然后用它来访问数组元素
for(int i=0; i != 10; i++){
*(sp.get() + i) = i; // 使用 get 获取内置指针
}