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;
    }
};
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 导语: C++指针的内存管理相信是大部分C++入门程序员的梦魇,受到Boost的启发,C++11标准推出了智能指针...
    7ee72f98ad17阅读 4,503评论 0 1
  • 12.1 智能指针 智能指针行为类似普通指针,但它负责自动释放所知的对象。 #include <memory> s...
    龙遁流阅读 2,920评论 0 1
  • 原作者:Babu_Abdulsalam 本文翻译自CodeProject,转载请注明出处。 引入### Ooops...
    卡巴拉的树阅读 30,275评论 13 74
  • C++裸指针的内存问题有:1、空悬指针/野指针2、重复释放3、内存泄漏4、不配对的申请与释放 使用智能指针可以有效...
    WalkeR_ZG阅读 8,387评论 0 5
  • shared_ptr 类 类似 vector,智能指针也是模板。因此,当我们创建一个智能指针时,必须提供额外的信息...
    赵者也阅读 4,308评论 0 0

友情链接更多精彩内容