《白话C++》第10章 STL和boost,Page101 10.4.6 std::weak_ptr

2.基本功能

“柔弱的”weak_ptr专门用来解决上述设计中必须面对的循环指向问题。

weak_ptr并不是真正的智能指针,它必须依附于shared_ptr存在。对应前面的C1、C2,我们写一个弱引用版本的C3和C4的例子:
 

struct C4;
struct C3
{~C3(){cout << "~C3" << endl;}weak_ptr <C4> _c4;
};struct C4
{~C4(){cout << "~C4" << endl;}weak_ptr <C3> _c3;
};void test_weak_reference()
{shared_ptr <C3> pc3(make_shared <C3> ());shared_ptr <C4> pc4(make_shared <C4> ());pc3->_c4 = pc4;pc4->_c3 = pc3;cout << "c3's ref count = " << pc3.use_count() << endl; // 1cout << "c4's ref count = " << pc4.use_count() << endl; // 1
}

019行,将一个shared_ptr,赋值给一个弱指针(weak_ptr),并不会造成shared_ptr的引用计数加1。因此后续输出时的内容才会是两个1。作为对比,将一个shared_ptr赋值给另一个shared_ptr(前例的情况),则会造成引用计数加1。

既然不增加人家的引用计数,弱指针在析构时,当然也不会减少人家的引用计数。为什么说“人家的”呢?

因为引用计数还是由那些shared_ptr在维护。弱指针其实一点也不“弱”,它不负责管理引用计数,却同样可以使用裸指针。

这种设计通常称为“观察者”模式。标准库的设计者,把weak_ptr给“阉割”了。

前面说过,weak_ptr不能算是真正的智能指针,因为它并没有直接模拟裸指针的行为。既不能使用“->”来访问所管理的指针,也不能使用“*”去访问所管理的裸指针所指向的对象。

如果我们希望通过weak_ptr操作裸指针,必须从该裸指针反过来创建一个shared_ptr:

shared_ptr <int> sp1(new int);
weak_ptr <int> wp(sp1); //shared->weak
cout << wp.use_count() << endl;// 1
//    *wp = 10; //不行shared_ptr <int> sp2(wp);//weak->shared
*sp2 = 10;

也可以通过weak_ptr提供的lock()方法得到一个shared_ptr;

shared_ptr <int> sp3 = wp.lock();
*sp3 = 11;

现在sp3、sp2、sp1有共同指向,引用计数为3,指向的数值是11。

不过,weak_ptr也有可能获取不到有效的裸指针。所有伙伴都退租了,因此从weak_ptr身上得到shared_ptr,有可能是空指针,为安全起见,应事先检查:

weak_ptr <int> wp; //空的弱指针
shared_ptr <int> sp = make_shared <int> (12);
wp = sp;//故意释放sp
sp.reset();//shared_ptr <int> sp2 = wp;
//assert(! sp2); //sp2铁定是nullptr

上面第8行,会报错:error: conversion from 'std::weak_ptr<int>' to non-scalar type 'std::shared_ptr<int>' requested|

另一个问题,既然引入弱指针目的是为了解决shared_ptr之间的“循环引用”问题,那我们又从弱指针创建出一个shared_ptr,“循环引用”岂不是又出现了?为此,通常从weak_ptr得到的shared_ptr,都只在一个临时代码内使用,用完就丢。以C3和C4为例,假设为C4增加一个IncC3Value()方法:

#include <iostream>
#include <memory>using namespace std;struct C4;
struct C3
{~C3() { cout << "~C3" << endl; }weak_ptr<C4> _c4;// 加一个没用的 int 入参,以告诉编译这是 后置++ (而非前置)C3& operator++(int){cout << "++\n";return *this;}
};struct C4
{~C4() {cout << "~C4" << endl; }weak_ptr <C3> _c3;void IncC3Value(){
// _c3 从弱指针,临时提升为 shared_ptr:然后出了它的临时作用域,
//这个智能指针就消失了(引用计数先加1,马上又减1);不会对当前的
//C4智能指针的最终析构带来什么影响。if (auto p = _c3.lock()){cout << "p's ref count = " << p.use_count() << endl;(*p)++;}}
};void test_weak_reference()
{shared_ptr<C3> pc3 (make_shared<C3>());shared_ptr<C4> pc4(make_shared<C4>());// 开始人为制作交叉引用:pc3->_c4 = pc4; // pc3 拥有 pc4pc4->_c3 = pc3; // pc4 拥有 pc3cout << "c3's ref count = " << pc3.use_count() << endl; //1cout << "c4's ref count = " << pc4.use_count() << endl; //1// 测试上面的函数pc4->IncC3Value();
}int main()
{test_weak_reference();
}

30行的 if ()条件中,临时从一个 weak_ptr 升级(lock,加锁后)得到 一个 shared_ptr,然后出于它的临时作用域 (也就是 if { … } 范围 ),这个智能指针就消失了(引用数先加1,马上就又减1);不会对当前的 C4 智能指针对象的最终析构 带什么影响。

有了weak_ptr帮助躲过循环引用,推荐“shared_ptr一体化”的设计方法,

就是,如果一类对象在创建之后需要在多处使用,特别是跨线程使用,那么应该在创建或声明的地方,就为它绑上shared_ptr。

无需质疑,shared_ptr相比裸指针肯定会带来性能损耗。最直接的,为保证并发环境下引用计数变化的准确性,shared_ptr一定在内部使用了相关锁操作,而这一定会带来性能损耗。

不过这一点点损耗在多数情况下,可以接受,反过来,裸指针内存管理哪怕只是出错一点点,却万万不可接受。

注意,“在创建或声明时就为它绑上shared_ptr”,这话听来简单,不就是在创建对象时,直接使用make_shared吗?不仅如此,实际上还包括各处需要用到该类对象的声明,包括类的成员数据,包括函数入参和返回值等,比如有一个类Coo:

class Coo{...};

如果我们在设计时就认为该类对象应当使用shared_ptr管理,那么首先建议在类中定义智能指针的别名,以方便使用:

class Coo
{
public:typedef std::shared_ptr <Coo> Ptr;...
};

有函数的入参或返回值用到它,则:

Coo::Ptr foo(int a, Coo::Ptr pcoo)
{...
}

注意,虽然别名叫做“Ptr”,但其实就智能指针对象而言,这里使用的是传值,会带来复制的成本,包括shared_ptr在复制对象时的必然的操作:加锁,然后让“引用计数”加1,这样做的好处是安全,且foo在使用时,该智能指针永远有效(除非它一开始就无效),如果我们使用引用技术:

void foo_ref(int a, CooPtr& pcoo)
{...
}

那么在函数体中,pcoo不一定一直有效,因为在复杂的并发情况下,foo_ref的调用可能是异步的,调用者可能在foo_ref还没执行完毕就先结束了,从而造成pcoo所引用的shared_ptr失效。

weak_ptr基本不用作函数的入参或返回值(特定目的下除外),它通常用作类成员,以化解循环引用。

但是,并不是说只要是类成员,就一定使用weak_ptr而不使用shared_ptr,恰恰相反,weak_ptr应该只用在可以打破循环的某一环皆可。

比如前例中的C3和C4,更好的设计是其中仅一个使用weak_ptr。

【课堂作业】:只需一环,打破“循环引用”

请重新设计C3或C4类,以便只在其中某个类上使用weak_ptr即可打破循引用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/479850.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c++类和对象新手保姆级上手教学(中)

前言&#xff1a; 类和对象中篇&#xff0c;这里讲到的前4个默认成员函数&#xff0c;是类和对象中的重难点&#xff0c;许多资料上的讲法都非常抽象&#xff0c;难以理解&#xff0c;所以我作出这篇总结&#xff0c;分享学习经验&#xff0c;以便日后复习。 目录 6个默认成员…

图像配准之HomographyNet

文章名称&#xff1a;Deep Image Homography Estimation&#xff0c;论文地址&#xff1a;https://arxiv.org/pdf/1606.03798.pdf&#xff0c;代码地址&#xff1a;GitHub - mazenmel/Deep-homography-estimation-Pytorch: Deep homography network with Pytorch 1、背景介绍 …

小程序 - 下拉刷新

1.启用方法 2.刷新事件监听 3.停止方法

Stable Diffusion WebUI 常用命令行参数

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里。 大家好&#xff0c;我是水滴~~ 本文主要讲解 Stable Diffusion WebUI 的一些常用命令行参数&#xff0c;内容详细的介绍了每一种参数的使用&#xff0c;并配有截图&#xff0c;非常适合初学者…

vue3+element Plus+ts 自定义主题色,以及生成主题色各种透明度

目录 思路 安装css-color-function【接收一个颜色值&#xff0c;生成不同的透明度】 获取后台配置的主题色或者使用ColorPicker修改主题色 最终结果如下 思路 本篇文章的主体思路是从element Plus官网引申而来。结合了我以前用vue2element-ui配置主题色生成透明度&#x…

6、内网安全-横向移动WmiSmbCrackMapExecProxyChainsImpacket

用途&#xff1a;个人学习笔记&#xff0c;有所借鉴&#xff0c;欢迎指正&#xff01; 前言&#xff1a; 在内网环境中&#xff0c;主机192.168.3.31有外网网卡能出网&#xff0c;在取得该主机权限后上线&#xff0c;搭建web应用构造后门下载地址&#xff0c;利用该主机执行相…

基于springboot实现的音乐网站

一、系统架构 前端&#xff1a;html | js | css | bootstrap 后端&#xff1a;springboot | mybatis 环境&#xff1a;jdk1.8 | mysql | maven 二、 代码及数据库 三、功能介绍 01. 登录页 02. 用户注册 03. 首页 04. 喜欢 05. 查询

MySQL篇之主从同步原理

一、原理 MySQL主从复制的核心就是二进制日志。 二进制日志&#xff08;BINLOG&#xff09;记录了所有的 DDL&#xff08;数据定义语言&#xff09;语句和 DML&#xff08;数据操纵语言&#xff09;语句&#xff0c;但不包括数据查询&#xff08;SELECT、SHOW&#xff09;语句。…

微服务篇之注册中心

一、eureka 1.eureka的作用 2.eureka工作流程 1. 服务提供者和服务消费者向注册中心注册服务信息&#xff0c;然后注册中心记录了对应的服务器地址。 2. 服务消费者从注册中心拉取服务提供者的信息。 3. 通过负载均衡找到对应的服务提供者地址。 4. 服务消费者远程调用对应的服…

『运维备忘录』之 Ln 文件链接命令详解

运维人员不仅要熟悉操作系统、服务器、网络等知识&#xff0c;甚至对于开发相关的也要有所了解。很多运维工作者可能一时半会记不住那么多命令、代码、方法、原理或者用法等等。这里我将结合自身工作&#xff0c;持续给大家更新运维工作所需要接触到的知识点&#xff0c;希望大…

OpenCV边缘检测与视频读写

原理 OpenCV中的边缘检测原理主要基于图像梯度的计算&#xff0c;包括一阶梯度和二阶梯度。 一阶梯度&#xff1a;它反映了图像亮度变化的速度。Sobel算法就是一种以一阶梯度为基础的边缘检测算法。它通过计算图像在水平和垂直方向上的梯度来检测边缘。这种方法简单有效&…

Java学习心得感悟

在我踏入Java学习的道路之前&#xff0c;我对编程只是一知半解&#xff0c;对于代码的世界充满了好奇和向往。然而&#xff0c;当我真正开始学习Java时&#xff0c;我才意识到&#xff0c;学习Java不仅仅是学习一门编程语言&#xff0c;更是一种思维方式和解决问题的能力的培养…