C++ 11

026 weak_ptr

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

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;
    }
};
上一篇下一篇

猜你喜欢

热点阅读