026 weak_ptr

作者: 赵者也 | 来源:发表于2020-03-03 10: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;
        }
    };
    

    相关文章

      网友评论

        本文标题:026 weak_ptr

        本文链接:https://www.haomeiwen.com/subject/onmxaqtx.html