12.1.5 unique_ptr
一个 unique_ptr “拥有”它所指向的对象。
与 shared_ptr 不同,某个时刻只能有一个 unique_ptr 指向一个给定对象。(相当于 unique_ptr 会“独占”其所指向的对象,引用计数固定为 1)
当 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,但是可以通过 release 或 reset 将指针的所有权从一个(非 const)的 unique_ptr 转移给另一个 unique_ptr:
unique_ptr<string> p2(p1.release()); // release 将 p1 释放为空, 返回内置指针
// 👆 所有权将从 p1 转移给 p2
unique_ptr<string> p3(new string("Trex"));
p2.reset(p3.release()); // reset 释放了 p2 原先指向的内存
// 👆 p2 释放内存之后, 接受其参数, 它是由 p3 释放的内置指针, 因此 p3 将所有权转交给 p2
reset 成员接受一个可选的指针参数,它令 unique_ptr 重新指向给定的指针。
调用 release 会切断 unique_ptr 和它原来管理的对象之间的联系。
传递 unique_ptr 参数和返回 unique_ptr
不能拷贝 unique_ptr 的规则有一个例外:可以拷贝或赋值一个将要被销毁的 unique_ptr。最常见的例子就是返回一个 unique_ptr:
unique_ptr<int> clone(int p) {return unique_ptr<int>(new int(p));
}
还可以返回一个局部对象的拷贝:
unique_ptr<int> clone(int p) {unique_ptr<int> ret(new int(p));// ...return ret;
}
向 unique_ptr 传递删除器
类似 shared_ptr,unique_ptr 默认情况下使用 delete 释放它所指向的对象。与 shared_ptr 一样,可以重载一个 unique_ptr 中默认的删除器。但是,unique_ptr 管理删除器的方式与 shared_ptr 不同。
重载一个 unique_ptr 中的删除器会影响到 unique_ptr 类型以及如何构造该类型的对象。必须在尖括号 unique_ptr 指向类型之后提供删除器类型。在创建或 reset 一个这种 unique_ptr 类型的对象时**,必须提供一个指定类型的可调用对象(删除器)**:
unique_ptr<objT, delT> p(new objT, fcn);
以下是一个更具体的例子,在该例当中,使用 unique_ptr 代替 shared_ptr:
void f(destination &d) {connection c = connect(&d);unique_ptr<connection, decltype(end_conenction)*> p(&c, end_connection);
}
12.1.6 weak_ptr
weak_ptr 是一种不控制所指向对象生存期的智能指针(shared_ptr 在内存地址引用计数为 0 时自动将动态内存进行回收,而 unique_ptr 独占动态内存,当 unique_ptr 被销毁时,与其一一对应的动态内存也被回收),它指向一个 shared_ptr 管理的对象。
将一个 weak_ptr 绑定到一个 shared_ptr 不会改变 shared_ptr 的引用计数。一旦最后一个指向对象的 shared_ptr 被销毁,对象就会被释放。即使有 weak_ptr 指向对象,对象也还是会被释放。
下表列出了 weak_ptr 的常用方法:
当我们创建一个 weak_ptr 时,要用一个 shared_ptr 来初始化它:
auto p = make_shared<int>(42);
weak_ptr<int> wp(p); // 使用 shared_ptr 来对 wp 进行初始化
// 👆 由于 wp 弱共享 p, p 的引用计数未变化
上例中 wp 和 p 指向相同的对象。由于是弱共享,创建 wp 不会改变 p 的引用计数;wp 指向的对象也可能会被释放掉。
由于对象不存在,不能使用 weak_ptr 直接访问对象,而必须调用 lock。lock 检查 weak_ptr 指向的对象是否仍然存在。如果存在,lock 返回一个指向共享对象的 shared_ptr(注意,lock 函数返回的是指向与 weak_ptr 共享对象的 shared_ptr)。与任何其它 shared_ptr 类似,只要此 shared_ptr 存在,它所指向的底层对象就会一直存在。例如:
if(shared_ptr<int> np = wp.lock()) { // np 不为空则条件成立// 在 if 中, np 与 p 共享对象// np 的作用域仅限于这个方块内
}
核查指针类
为了展示 weak_ptr 的用途,下例为 StrBlob 定义一个伴随指针类。将这个指针类命名为 StrBlobPtr,它会保存一个 weak_ptr,指向 StrBlob 的 data 成员,这是初始化时提供给它的。
通过使用 weak_ptr,不会影响一个给定的 StrBlob 所指的 vector 的生存期。但可以阻止用户访问一个不再存在的 vector 的企图。
StrBlobPtr 有两个数据成员:wptr,或者为空或者指向一个 StrBlob 中的 vector;curr,保存当前对象所表示的元素的下标。类似它的伴随类 StrBlob,我们的指针类也有一个 check 成员来检查解引用 StrBlobPtr 是否安全:
class StrBlobPtr {public:StrBlobPtr(): curr(0) {}StrBlobPtr(StrBlob &a, size_t sz = 0): wptr(a.data), curr(sz) { }std::string& deref() const;StrBlobPtr& incr(); // 前缀递增private:std::shared_ptr<std::vector<std::string>> check(std::size_t, const std::string&) const;std::weak_ptr<std::vector<std::string>> wptr;std::size_t curr;
};
默认构造函数生成一个空的 StrBlobPtr。其构造函数初始化列表将 curr 显式初始化为 0,并将 wptr 隐式初始化为一个空的 weak_ptr。
第二个构造函数接受一个 StrBlob 的引用,和一个可选的索引值。此构造函数初始化 wptr,令其指向给定 StrBlob 对象的 shared_ptr 中的 vector,并将 curr 初始化为 sz。
值得注意的是,不能将 StrBlobPtr 绑定到一个非 const StrBlob 对象。这个限制是由于构造函数接受一个非 const StrBlob 对象的引用导致的。
StrBlobPtr 的 check 成员与 StrBlob 中的同名成员不同,它还要检查指针指向的 vector 是否存在:
std::shared_ptr<std::vector<std::string>> StrBlobPtr::check(std::size_t i, const std::string &msg) const {auto ret = wptr.lock(); // 检查 vector 是否还存在if(!ret) {throw std::runtime_error("unbound StrBlobPtr");}if(i >= ret -> size()) {throw std::out_of_range(msg);}return ret; // 否则, 返回指向 vector 的 shared_ptr
}
指针操作
定义名为 deref 和 incr 的函数,分别用来解引用和递增 StrBlobPtr。
deref 成员调用 check,检查使用 vector 是否安全,以及 curr 是否在合法范围内:
std::string& StrBlobPtr::deref() const {auto p = check(curr, "dereference past end");return (*p)[curr]; // (*p)是对象所指向的 vector
}
incr 成员也调用 check:
// 👇 前缀递增, 返回递增后的对象的引用
StrBlobPtr& StrBlobPtr::incr() {// 👇 如果 curr 已经指向容器的尾部位置, 则不能再递增check(curr, "increment past end of StrBlobPtr");++ curr; // 推进当前位置return *this;
}
为了能够访问 data 成员,必须在 StrBlob 的定义当中将 StrBlobPtr 声明为 friend。我们还要为 StrBlob 类定义 begin 和 end,返回一个指向它自身的 StrBlob:
class StrBlob {public:friend class StrBlobPtr;StrBlobPtr begin() { return StrBlobPtr(*this); }StrBlobPtr end() { auto ret = StrBlobPtr(*this, data -> size());return ret; }// 其它成员与 12.1.1 中的声明相同