026 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 来初始化它:
auto p = make_shared<int>(42);
weak_ptr<int> wp(p); //wp 弱共享 p;p 的引用计数未改变
本例中 wp 和 p 指向相同的对象,由于是弱共享,创建 wp 不会改变 p 的引用计数;wp 指向的对象可能被释放掉。
由于对象可能不存在,我们不能使用 weak_ptr 直接访问对象,而必须调用 lock。此函数检查 weak_ptr 指向的对象是否仍存在,如果存在,lock 返回一个指向共享对象的 shared_ptr。与任何其他 shared_ptr 类似,只要此 shared_ptr 存在,它所指向的底层对象也就会一直存在。例如:
if (shared_ptr<int> np = wp.lock()) { // 如果 np 不为空则条件成立
// 在 if 中,np 与 p 共享对象
}
在这段代码中,只有当 lock 调用返回 true 时我们才会进入 if 语句体。在 if 中,使用 np 访问共享对象是安全的。
核查指针类
作为 weak_ptr 用途的一个展示,我们将为 StrBlob 类定义一个伴随指针类。我们的指针类将命名为 StrBlobPtr,会保存一个 weak_ptr,指向 StrBlob 的 data 成员,这是初始化时提供给它的,通过使用 weak_ptr,不会影响一个给定的 StrBlob 所指向的 vector 的生存期。但是,可以阻止用户访问一个不再存在的 vector 的企图。
StrBlobPtr 会有两个数据成员:wptr,或者为空,或者指向一个 StrBlob 中的 vector;curr,保存当前对象所表示的元素的下标,类似它的伴随类 StrBlob,我们的指针类也有一个 check 成员来检查解引用 StrBlobPtr 是否安全:
// 对于访问一个不存在元素的尝试,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:
// 若检查成功,check 返回一个指向 vector 的 shared_ptr
std::shared_ptr<std::vector<std::string>>
check(std::size_t, const std::string& ) const;
// 保存一个 weak_ptr,意味看底层 vector 可能会被销毁
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 的值。我们使用了默认参数,表示默认情况下将 curr 初始化为第一个元素的下标。我们将会看到,StrBlob 的 end 成员将会用到参数 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
}
由于一个 weak_ptr 不参与其对应的 shared_ptr 的引用计数,StrBlobPtr 指向的 vector 可能已经被释放了。如果 vector 已销毁,lock 将返回一个空指针。在本例中,任何 vector 的引用都会失败,于是抛出一个异常。否则,check 会检查给定索引,如果索引值合法,check 返回从 lock 获得的 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
}
如果 check 成功,p 就是一个 shared_ptr,指向 StrBlobPtr 所指向的 vector。表达式 (*p)[curr] 解引用 shared_ptr 来获得 vector,然后使用下标运算符提取并返回 curr 位置上的元素。
incr 成员也调用 check:
// 前缀递增:返回递增后的对象的引用
StrBlobPtr& StrBlobPtr::incr() {
// 如果 curr 已经指向容器的尾后位置,就不能递增它
check(curr, "increment past end of StrBlobPtr");
++curr; // 推进当前位置
return *this;
}
当然,为了访问 data 成员,我们的指针类必须声明为 StrBlob 的 friend 我们还要为 StrBlob 类定义 begin 和 end 操作,返回一个指向它自身的 StrBlobPtr:
// 对于 StrBlob 中的友元声明来说,此前置声明是必要的
class StrBlobPtr;
class StrBlob {
friend class StrBlobPtr;
// 其他成员
// 返回指向首元素和尾后元素的 StrBlobPtr
StrBlobPtr begin() {return StrBlobPtr(*this); }
StrBlobPtr end() {
auto ret = StrBlobPtr(*this, data->size());
return ret;
}
};