C++ 11

025 unique_ptr

2020-03-02  本文已影响0人  赵者也

一个 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不支持赋值

shared_ptr 和 unique_ptr 都支持的操作

操作 说明
shared_ptr<T> sp
unique_ptr<T> up
空智能指针,可以指向类型为 T 的对象
p 将 p 用作一个条件判断,若 p 指向一个对象,则为 true
*p 解引用 p,获得它指向的对象
p->mem 等价于(*p).mem
p.get() 返回 p 中保存的指针。要小心使用,若智能指针释放了其对象,返回的指针所指向的对象也就消失了
swap(p, q)
p.swap(q)
交换 p 和 q 中的指针

只适用于 unique_ptr 的操作

操作 说明
unique_ptr<T> u1 空 unique_ptr,可以指向类型为 T 的对象 u1 会使用 delete 来释放它的指针
unique_ptr<T, D> u2 空 unique_ptr,可以指向类型为 T 的对象 u2 会使用一个类型为 D 的可调用对象来释放它的指针
unique_ptr<T, D> u(d) 空 unique_ptr,指向类型为 T 的对象,用类型为 D 的对象 d 代替 de1ete
u = nullptr 释放 u 指向的对象,将 u 置为空
u.release() u 放弃对指针的控制权,返回指针,并将 u 置为空
u.reset() 释放 u 指向的对象
u.reset(q)
u.reset(nullptr)
如果提供了内置指针 q,令 u 指向这个对象;否则将 u 置为空

虽然我们不能拷贝或赋值 unique_ptr,但可以通过调用 release 或 reset 将指针的所有权从一个(非 const)unique_ptr 转移给另一个 unique:

// 将所有权从 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 被置为空。

reset 成员接受一个可选的指针参数,令 unique_ptr 重新指向给定的指针,如果 unique_ptr 不为空,它原来指向的对象被释放。因此,对 p2 调用 reset 释放了用 “Stegosaurus” 初始化的 string 所使用的内存,将 p3 对指针的所有权转移给 p2,并将 p3 置为空。

调用 release 会切断 unique_ptr 和它原来管理的对象间的联系。release 返回的指针通常被用来初始化另一个智能指针或给另一个智能指针赋值。在本例中,管理内存的责任简单地从一个智能指针转移给另一个,但是,如果我们不用另一个智能指针来保存 release 返回的指针,我们的程序就要负责资源的释放:

p2.release(); // 错误:p2 不会释放内存,而且我们丢失了指针
auto p = p2.release(); // 正确,但我们必须记得 delete(p)

传递 unique_ptr 参数和返回 unique_ptr

不能拷贝 unique_ptr 的规则有一个例外:我们可以拷贝或赋值一个将要被销毁的 unique_ptr,最常见的例子是从函数返回一个 unique_ptr:

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;
}

对于两段代码,编译器都知道要返回的对象将要被销毁。

向后兼容:auto_ptr

标准库的较早版本包含了一个名为 auto_ptr 的类,它具有unique_ptr 的部分特性,但不是全部。特别是,我们不能在容器中保存 auto_ptr,也不能从函数中返回 auto_ptr。虽然 auto_ptr 仍是标准库的一部分,但编写程序时应该使用 unique_ptr。

向 unique_ptr 传递删除器

类似 shared_ptr,unique_ptr 默认情况下用 delete 释放它指向的对象,与 shared_ptr 一样,我们可以重载一个 unique_ptr 中默认的删除器。但是,unique_ptr 管理删除器的方式与 shared_ptr 不同。

重载一个 unique_ptr 中的删除器会影响到 unique_ptr 类型以及如何构造(或 reset)该类型的对象。与重载关联容器的比较操作类似我们必须在尖括号中 unique_ptr 指向类型之后提供删除器类型。在创建或 reset 一个这种 unique_ptr 类型的对象时,必须提供一个指定类型的可调用对象(删除器):

// p 指向一个类型为 objT 的对象,开使用一个类型为 delT 的对象释放 objT 对象
// 它会调用一个名为 fcn 的 delT 类型对象
unique_ptr<objT, delT> p(new objT, fcn);

作为一个更具体的例子,我们将重写连接程序,用 unique_ptr 米代替 shared_ptr 如下所示:

void f(destination &d /* 其他需要的参数 */) {
    connection c = connect(&d); // 打开连接
    // 当 p 被销毁时,连接将会关闭
    unique_ptr<connection, decltype(end_connection)*> p(&c, end_connection):
    // 使用连接
    // 当 f 退出时(即使是由于异常而退出),connection 会被正确关闭
}

在本例中我们使用了 decltype 来指明函数指针类型。由于 decltype(end_connection)返回一个函数类型,所以我们必须添加一个 * 来指出我们正在使用该类型的一个指针。

unique_ptr 和动态数组

标准库提供了一个可以管理 new 分配的数组的 unique_ptr 版本。为了用一个 unique_ptr 管理动态数组,我们必须在对象类型后面跟一对空方括号:

// up 指向一个包含 10 个未初始化 int 的数组
unique_ptr<int[]> up(new int[10]);
up.release(); // 自动用 delete[] 销毁其指针

类型说明符中的方括号(<int[]>)指出 up 指向一个 int 数组而不是一个 int。由于 up 指向一个数组,当 up 销毁它管理的指针时,会自动使用 delete[]。

指向数组的 unique_ptr 提供的操作与我们在中使用的那些操作有一些不同。当一个 unique_ptr 指向一个数组时,我们不能使用点和箭头成员运算符。毕竟 unique_ptr 指向的是一个数组而不是单个对象,因此这些运算符是无意义的。另一方面,当一个 unique_ptr 指向一个数组时我们可以使用下标运算符来访问数组中的元素:

for (size_t i = 0; i != 10; ++i) {
    up[i] = i; // 为每个元素赋予一个新值
}
指向数组的 unique_ptr 说明
指向数组的 unique_ptr 不支持成员访问运算符(点和箭头运算符) 其他 unique_ptr 操作不变
unique_ptr<T[]> u u 可以指向一个动态分配的数组,数组元素类型为 T
unique_ptr<T[]> u(p) u 指向内置指针 p 所指向的动态分配的数组。p 必须能转换为类型 T*
u[i] u 必须指向一个数组,返回 u 拥有的数组中位置 i 处的对象
上一篇下一篇

猜你喜欢

热点阅读