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

news2025/1/16 19:10:02

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; // 1
    cout << "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 拥有 pc4
    pc4->_c3 = pc3; // pc4 拥有 pc3

    cout << "c3's ref count = " << pc3.use_count() << endl; //1
    cout << "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.coloradmin.cn/o/1458780.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

社区店运营方案全攻略,助你实现商业梦想

在如今竞争激烈的商业环境中&#xff0c;成功运营一家社区店需要全面的规划和策略。 作为一名在社区开鲜奶吧5年的创业者&#xff0c;我将为你提供一份全面的社区店运营方案攻略&#xff0c;帮助你实现商业梦想。 1、市场调研&#xff1a; 在选择社区店的位置之前&#xff0c…

Java使用Redis实现消息队列

近期刷Java面试题刷到了“如何使用Redis实现消息队列”&#xff0c;解答如下&#xff1a; 一般使用 list 结构作为队列&#xff0c; rpush 生产消息&#xff0c; lpop 消费消息。当 lpop 没有消息的时候&#xff0c;要适当sleep 一会再重试。若不使用sleep&#xff0c;则可以用…

【C++】STL- > string类(超详解!!!)

文章目录 前言1、string类的出现1.1 C语言中的字符串1.2 平时使用 2. 标准库中的string类2.1 string类的常用文档&#xff08;重要&#xff09;&#xff01;&#xff01;&#xff01;&#xff01;2.2 string类的常用接口说明(接口原型我这里就不展示了&#xff0c;文档中都有可…

springcloud-远程调用

微服务的远程调用 RestTemplate 在项目中&#xff0c;当我们需要远程调用一个 HTTP 接口时&#xff0c;我们经常会用到 RestTemplate 这个类。这个类是 Spring 框架提供的一个工具类。 实例化RestTemplate 创建配置类&#xff0c;实例化RestTemplate Configuration public clas…

ubuntu解决“E: Unable to locate package lrzsz“

今天在ubuntu上安装rzsz包时报错&#xff0c;提示无法定位包&#xff0c;提示如下 出现这个问题是因为apt的源没有更新&#xff0c;我们直接说解决办法 把下面的命令执行一遍即可 sudo add-apt-repository main sudo add-apt-repository universe sudo add-apt-repository re…

【漏洞复现】H3C 路由器多系列信息泄露漏洞

Nx01 产品简介 H3C路由器是一款高性能的路由器产品&#xff0c;具有稳定的性能和丰富的功能。它采用了先进的路由技术和安全机制&#xff0c;可以满足不同用户的需求&#xff0c;广泛应用于企业、运营商和数据中心等领域。 Nx02 漏洞描述 H3C路由器多系列存在信息泄露漏洞&…

“薪”的一年程序员裁员潮技术变革情况下 程序员就业机会在哪里?

引言&#xff1a;一对来自中国的工程师夫妻在美国的不幸身亡&#xff0c;疑似与谷歌的裁员有关&#xff0c;这一事件再次引发了人们对技术变革下裁员对程序员影响的关注。 一、针对裁员潮的一些看法 在我看来&#xff0c;技术变革对程序员的影响是双面的。一方面&#xff0c;…

抛弃chatgpt,使用微软的Cursor提升coding效率

Whats Cursor? Cursor编辑器是一个基于GPT-4的代码编辑器&#xff0c;它可以根据用户的自然语言指令或者正在编辑的代码上下文为用户提供代码建议&#xff0c;支持多种编程语言&#xff0c;如Python、Java、C/C#、go等。Cursor编辑器还可以帮助用户重构、理解和优化代码&…

管式土壤墒情监测仪

TH-GTS04随着农业技术的不断进步&#xff0c;土壤墒情监测在农业生产中的作用越来越突出。管式土壤墒情监测仪作为一种先进的土壤水分测量工具&#xff0c;为农业生产提供了准确、实时的土壤水分数据&#xff0c;为科学决策提供了有力支持。 一、管式土壤墒情监测仪的工作原理…

【力扣 - 二叉树的最大深度】

题目描述 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 提示&#xff1a; 树中节点的数量在 [0, 10^4] 区间内。 -100 < Node.val < 100方法一&#xff1a;深度优先搜索 思路与算法 如…

Redis第一关之常规用法

简介 Redis不用多说&#xff0c;已经火了很多年了&#xff0c;也用了很多年了。现在做一些归纳总结。 这篇文章主要介绍Redis的常规知识及用法&#xff0c;包括数据结构、使用场景、特性、过期机制、持久化机制。 Redis与Mysql Mysql是一款基于磁盘的关系型SQL数据库。 Redi…

软件实际应用实例分享,门诊电子处方模板制作教程,中西医诊所病历开单系统教程

软件实际应用实例分享&#xff0c;门诊电子处方模板制作教程&#xff0c;中西医诊所病历开单系统教程 一、前言 以下软件教程以 佳易王诊所电子处方软件V17.3为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 1、在开电子处方的时候&#xff0c…

RocketMQ(五):功能特性——消费管理

1 消费者负载均衡 消费者从Apache RocketMQ获取消息消费时&#xff0c;通过消费者负载均衡策略&#xff0c;可以将主题内的消息分配给指定消费者分组中的多个消费者共同分担&#xff0c;提高消费并发能力和消费者的水平扩展能力。 1.1 背景信息 了解消费者负载均衡策略&#x…

文献学习-1-Continuum Robots for Medical Interventions

Chapt 5. 连续体机构分析 5.1 文献学习 5.1.1 Continuum Robots for Medical Interventions Authors: PIERRE E. DUPONT , Fellow IEEE, NABIL SIMAAN , Fellow IEEE, HOWIE CHOSET , Fellow IEEE, AND CALEB RUCKER , Member IEEE 连续体机器人在医学上得到了广泛的应用&a…

“比特币突破5.2万美元”,一枚币可换一斤半黄金?黄金比特币之争再次甚嚣尘上!

自今年1月美国SEC批准比特币现货ETF登陆美股市场之后&#xff0c;只用了短短的30个交易日&#xff0c;比特币ETF就从零膨胀到了近400亿美元的规模&#xff0c;超过白银ETF约100多亿美元的规模&#xff0c;和规模约为900多亿美元的黄金ETF暂时形成了“三七开”的格局。比特币现货…

SpringBoot自动注入源码分析

Spring Boot何时注入Autowired标注的属性&#xff1f; 是在Bean实例化后&#xff0c;填充Bean的时候注入Autowired标注的属性 如果注入类型的Bean存在多个&#xff0c;Spring Boot是如何处理的&#xff1f; 如果存在多个类型的Bean&#xff0c;会根据primary—>javax.ann…

电脑屏幕录制工具 Top10 榜单,免费无水印方法集

随着媒体行业的突飞猛进&#xff0c;不同服务之间对有效屏幕录制的竞争日益激烈。这导致市场上出现了质量参差不齐的屏幕录像机。特别是有些录屏器会自动给你录制的视频加上水印&#xff0c;给需要在公共场合使用的人留下不专业的印象。除此之外&#xff0c;它们甚至不能保护您…

【postgresql】ERROR: relation “data_screen.import_record_id_seq“ does not exist

创建表时候提示下面错误&#xff1a; ERROR: relation "data_screen.import_record_id_seq" does not exist 错误&#xff1a;关系“data_screen.import_record_id_seq”不存在 创建语句 CREATE TABLE "data_screen"."import_record" ("…

[框架系列]-[通用lock框架]

通用lock框架 项目特点一:框架集成1.引入核心依赖2.使用redis厂商lock默认实现Redisson3.使用zookeeper厂商lock 二:api调用示例三:注解支持1.注解介绍2.注解使用示例 四:lock增强五:全局锁降级六:配置1.配置清单2.具体配置介绍3.配置demo 七:CUSTOMER的SPI扩展示例1.SPI扩展点…

人工智能实训室解决方案2024

人工智能实训室解决方案 一、专业背景 人工智能是一门新兴的技术科学&#xff0c;旨在模拟、延伸和扩展人的智能。它涉及多个领域&#xff0c;如机器人、语言识别、图像识别、自然语言处理和专家系统等。人工智能的实际应用广泛&#xff0c;包括机器视觉、指纹识别、人脸识别…