std::ref用法以及和引用区别

news/2024/5/8 1:13:26/文章来源:https://blog.csdn.net/qq_44918090/article/details/127354840

1、std::ref是什么?

  • 关于c++中的std::ref,std::ref在c++11引入。本文通过讲解std::ref的常用方式,及剖析下std::ref内部实现,然后我们再进一步分析为什么使用std::ref。

  • ref是个函数模板:
    在这里插入图片描述

    • 用来构建一个reference_wrapper对象并返回,该对象拥有传入的elem变量的引用。如果参数本身是一个reference_wrapper类型的对象,则创建该对象的一个副本,并返回。

2、为什么要有std::ref

  • std::ref主要在函数式编程(如std::bind)时使用,bind是对参数直接拷贝,无法传入引用(即使你传入的实参是引用类型也不行),故引入std::ref()。使用std::ref可以在模板传参的时候传入引用。

  • std::ref能使用reference_wrapper包装好的引用对象代替原本会被识别的值类型,而reference_wrapper能隐式转换为被引用的值的引用类型。

代码如下:

#include <iostream>
#include <functional>
#include<vector>using namespace std;//std::ref主要是考虑函数式编程(如std::bind)在使用时,是对参数直接拷贝,而不是引用
void f(int& a, int& b, int& c)
{cout << "in function a = " << a << "  b = " << b << "  c = " << c << endl;cout << "in function a = " << &a << "  b = " << &b << "  c = " << &c << endl;a += 1;b += 10;c += 100;
}int main() {int n1 = 1, n2 = 10, n3 = 100;int& r1 = n1;int& r2 = n2;function<void()> f1 = bind(f, r1, r2, ref(n3));		//前两个参数即便是引用类型,bind 传入的还是其值的拷贝,第三个参数传入 reference_wrapper 对象,该对象可隐式的转换为值的引用f1();cout << "out function a = " << n1 << "  b = " << n2 << "  c = " << n3 << endl;cout << "out function a = " << &n1 << "  b = " << &n2 << "  c = " << &n3 << endl;f1();cout << "out function a = " << n1 << "  b = " << n2 << "  c = " << n3 << endl;cout << "out function a = " << &n1 << "  b = " << &n2 << "  c = " << &n3 << endl;return 0;
}

输出结果:

in function a = 1  b = 10  c = 100
in function a = 0000006B90EFF710  b = 0000006B90EFF708  c = 0000006B90EFF684
out function a = 1  b = 10  c = 200
out function a = 0000006B90EFF644  b = 0000006B90EFF664  c = 0000006B90EFF684
in function a = 2  b = 20  c = 200
in function a = 0000006B90EFF710  b = 0000006B90EFF708  c = 0000006B90EFF684
out function a = 1  b = 10  c = 300
out function a = 0000006B90EFF644  b = 0000006B90EFF664  c = 0000006B90EFF684

3、std::ref和引用的区别

  • 首先就是,上面的例子里,使用bind的时候,普通引用和std::ref引用有区别。

  • std::ref只是尝试模拟引用传递,并不能真正变成引用,在非模板情况下,std::ref根本没法实现引用传递,只有模板自动推导类型或类型隐式转换时,std::ref能用包装类型reference_wrapper来代替原本会被识别的值类型,而reference_wrapper能隐式转换为被引用的值的引用类型。

  • 目前我只遇到过类型转换时,ref和普通引用的区别,模板自动推导类型的情况还没遇到过。

4、std::ref 用法

int n1 = 0;
auto n2 = std::ref(n1);n2++;
n1++;std::cout << n1 << std::endl;  // 2
std::cout << n2 << std::endl;  // 2
  • 可以看到 是把n1的引用传递给了n2,分别进行加法,可以看到n2是n1的引用,最终得到的值都是2。

  • 那么大家可能会想,我都已经有了’int& a = b’的这种引用赋值的语法了,为什么c++11又出现了一个std::ref,我们继续来看例子:

    #include <iostream>
    #include <thread>void thread_func(int& n2) { // error, >> int n2n2++;
    }int main() {int n1 = 0;std::thread t1(thread_func, n1);t1.join();std::cout << n1 << std::endl;
    }
    • 我们如果写成这样是编译不过的,除非是去掉引用符号,那么我如果非要传引用怎么办呢?

      // snap ...int main() {int n1 = 0;std::thread t1(thread_func, std::ref(n1));t1.join();std::cout << n1 << std::endl; // 1
      }
      
  • 这样可以看到引用传递成功,并且能够达到我们效果,我们再来看个例子:

#include <iostream>
#include <functional>void func(int& n2) {n2++;
}int main() {int n1 = 0;auto bind_fn = std::bind(&func, std::ref(n1));bind_fn();std::cout << n1 << std::endl; // 1
}
  • 这里我们也发现std::bind这样也是需要通过std::ref来实现bind引用。

    • 那么我们其实可以看的出来,std::bind或者std::thread里是做了什么导致我们原来的通过&传递引用的方式失效,或者说std::ref是做了什么才能使得我们使用std::bind和std::thread能够传递引用。

    • 那么我们展开std::ref看看他的真面目,大致内容如下:

template <class _Ty>
reference_wrapper<_Ty> ref(_Ty& _Val) noexcept {return reference_wrapper<_Ty>(_Val);
}
  • 这里我们看到std::ref最终只是被包装成reference_wrapper返回,所以关键点还是std::reference_wrapper。

5、 为什么使用std::ref

  • 我们看下为什么std::bind或者std::thread为什么要使用reference_wrapper,我们以std::bind为例子吧,我们大致去跟踪下std::bind,跟踪的目的是看传递bound参数(即我们传给bind函数的参数)的生命周期,以vs2019的实现为例:
template <class _Fx, class... _Types>
_NODISCARD _CONSTEXPR20 _Binder<_Unforced, _Fx, _Types...> bind(_Fx&& _Func, _Types&&... _Args) {return _Binder<_Unforced, _Fx, _Types...>(_STD forward<_Fx>(_Func), _STD forward<_Types>(_Args)...);
}
  • 看到是构造了一个_Binder的对象返回,bound参数作为构造函数的参数传入。

    using _Second = tuple<decay_t<_Types>...>; //std::decay_t会移除掉引用属性
    _Compressed_pair<_First, _Second> _Mypair;constexpr explicit _Binder(_Fx&& _Func, _Types&&... _Args): _Mypair(_One_then_variadic_args_t{}, _STD forward<_Fx>(_Func), _STD forward<_Types>(_Args)...) {}
  • 也可以看到构造函数中,参数传递给_Mypair成员。到这里结束。

  • 我们再看下调用时:

#define _CALL_BINDER                                                                  \_Call_binder(_Invoker_ret<_Ret>{}, _Seq{}, _Mypair._Get_first(), _Mypair._Myval2, \_STD forward_as_tuple(_STD forward<_Unbound>(_Unbargs)...))template <class... _Unbound>_CONSTEXPR20 auto operator()(_Unbound&&... _Unbargs) noexcept(noexcept(_CALL_BINDER)) -> decltype(_CALL_BINDER) {return _CALL_BINDER;
}
  • 看到调用时会用到_CALL_BINDER宏,这里调用_Call_binder函数,并把_Mypair传入,再接下来就会调用到我们的函数并传入bound的参数了。_
  • _总结下就是std::bind首先将传入的参数存放起来,等到要调用bind的函数就将参数传入,而这里没有保存传入参数的引用,只能保存一份参数的拷贝,如果使用我们上边说的“int& a = b”语法,_Binder类中无法保存b的引用,自然调用时传入的就不是b的引用,所以借助reference_wrapper将传入参数的地址保存,使用是通过地址取出来值进而调用函数。

6、std::ref总结

  • 我来给总结下,首先我们讲解了std::ref的一些用法,然后我们讲解std::ref是通过std::reference_wrapper实现,然后我们借助了cppreference上的实现来给大家剖析了他本质就是存放了对象的地址(类似指针的用法😁),还讲解了noexcept等语法,最后我们讲解了下std::bind为什么要使用到reference_wrapper。
  • std::bind使用的是参数的拷贝而不是引用,当可调用对象期待入参为引用时,必须显示利用std::ref来进行引用绑定。
  • 多线程std::thread的可调用对象期望入参为引用时,也必须显式通过std::ref来绑定引用进行传参。

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

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

相关文章

【区块链 | Solidity】跟我学 Solidity(6):工厂模式

如何在智能合约中使用clone工厂模式减少合约部署 gas。 原文 https://medium.com/better-programming/learn-solidity-the-factory-pattern-75d11c3e7d29 作者 Wissal haji欢迎来到学习 Solidity 系列的另一部分。在上一篇文章,我们讨论了如何从智能合约中创建另一个智能合约。…

泛型(1)

泛型的概述 泛型是一种未知的数据类型 当我们不知道使用什么数据类型的时候 可以使用泛型 泛型也可以看出是一个变量 用来接收数据类型 当我们创建集合是就确定泛型类型 使用泛型的好处 我们都知道集合中是可以存放任意对象的 只要把对象存储集合后 那么这时他们都会被提升成Ob…

无向图的双连通分量 DCC

双连通分量又称为重连通分量 分为 (1)边的双连通分量 EDCC &#xff1a;极大的不含桥的连通区域&#xff08;块&#xff09; 性质: 边的双连通分量不管删掉哪条边都是连通的 任意两点之间都包含两条不相交的路径&#xff08;充分必要&#xff09; (2)点的双连通分量 VDCC &…

电压掉电监测电路-三极管电路分析

电压掉电监测电路 电路在电压掉电时处于不稳定状态&#xff0c;经常需要采取一些应对措施。比如音响&#xff0c;内部的音频功率放大电路&#xff0c;在被突然拔掉电源时会发出刺耳的爆破音。 如果加入电压掉电监测电路&#xff0c;当监测到电压掉电时&#xff0c;输出一个信号…

MySQL目录结构与SQL基本概念

MySQL目录结构 1.MySQL安装目录配置文件 my.ini1、bin目录 用于放置一些可执行文件,如mysql.exe、mysqld.exe、mysqlshow.exe等。 2、data目录 用于放置一些日志文件以及数据库。 3、include目录 用于放置一些头文件,如:mysql.h、mysql_ername.h等。 4、lib目录 用于放置一系…

刷爆leetcode第七期 0018

刷爆leetcode第七期 0018题目编号0018 用队列实现栈第一步 定义结构体第二步 实现创建&#xff08;初始化&#xff09;第三步 删除接口函数第四步 返回头的值总结发现问题一发现问题二源码题目编号0018 用队列实现栈 请你仅使用两个队列实现一个后入先出&#xff08;LIFO&…

.NET周报【10月第1期 2022-10-11】

本周精选 继C#实现await/async无栈协程几年后,davidwrighton实现了.NET绿色线程(有栈协程)的原型 https://github.com/dotnet/runtimelab/pull/2002 .NET Runtimelab中绿色线程的原型实现的PR,在不久的将来,.NET开发者也可以方便的用上有栈协程,目前的启动一个有栈协程的AP…

docker:基础命令未完待续

基础操作 docker info #查看docker的基本信息docker version #查看docker版本信息一、镜像操作 1、搜索镜像 docker search nginx2、下载镜像 docker pull nginx#从仓库中下载镜像&#xff0c;若没有指定标签&#xff0c;则下载最新的版本&#xff0c;也就是标签为: la…

Python快速刷题网站——牛客网 数据分析篇(十五)

&#x1f466;&#x1f466;一个帅气的boy&#xff0c;你可以叫我Love And Program &#x1f5b1; ⌨个人主页&#xff1a;Love And Program的个人主页 &#x1f496;&#x1f496;如果对你有帮助的话希望三连&#x1f4a8;&#x1f4a8;支持一下博主 前言 本文将继续学习pan…

MySQL的行锁、间隙锁和临建锁

目录 行锁 间隙锁&临键锁 行锁 InnoDB实现了以下两种类型的行锁&#xff1a; 共享锁&#xff08;S&#xff09;&#xff1a;允许一个事务去读一行&#xff0c;阻止其他事务获得相同数据集的排它锁。 //共享锁和共享锁兼容&#xff0c;共享锁和排他锁互斥。 排他锁&#…

43 多个相同限定名类型同时存在导致的继承结构混乱的情况

前言 // 四刷天府绿道 呵呵 在前面文章中 jetty-runner:jar:9.3.20 和 tomcat-embed-core-8.5.29 的 JarScannerCallback 不兼容, 导致服务启动失败 提到了这样的一个问题 我们再看一下这里的 callback 的接口, jetty-runner 的这个对象里面是没有 void scan(Jar jar, Str…

【附源码】计算机毕业设计SSM民宿短租系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

JavaEE - Servlet(向服务器上传文件 Part类)

我们在需要向服务器上传文件时&#xff0c;在前端需要使用form表单&#xff0c;form表单需要使用特殊的类型 form-data 此时提交文件的时候&#xff0c;浏览器会把文件内容以form-data的格式构造到HTTP请求中&#xff0c;服务器就可以通过getPart获取了 需要注意&#xff1a;…

2.idea 标定相关

1.发现 VINS对于参数准确性的要求高于ORBSLAM。依据是相同的参数,ORBSLAM可以提供准确的定位结果,但是VINS很容易就会发散。在线标定外参很有效,经历过几次外参标定以后的外参给VINS可以获得很好的效果,但是不排除只是针对这个场景,随后测试如果效果好,考虑给ORBSLAM3增加…

Redis常见的问题

① 缓存雪崩 缓存雪崩是指在短时间内&#xff0c;有⼤量缓存同时过期&#xff0c;导致⼤量的请求直接查询数据库&#xff0c;从⽽对数据库造成 了巨⼤的压⼒&#xff0c;严重情况下可能会导致数据库宕机的情况叫做缓存雪崩。 我们先来看下正常情况下和缓存雪崩时程序的执⾏流…

docker安装tomcat、mysql、redis

一、tomcat 1.下载tomcat8docker pull tomcat:8.5.612.启动容器(-d 后台启动)docker run -d -p 8080:8080 tomcat:8.5.61 3.访问首页http://ip:8080/访问不到 404 解决:需要修改tomcat下的文件夹 如下 进入后webapps.dist改为webapps 二、mysql 1.拉取mysqldocker pull mys…

网课题搜答案公众号接口系统

网课题搜答案公众号接口系统 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 查题校园题库&#xff1a;查题校园题库后台&#xf…

分布式数据库的基本概念

1.分布式数据库系统的产生和定义 产生原因: 经济的发展&#xff1a;经济发展&#xff1a;跨国公司&#xff1a;产生一个地方需要管理另外一个地方数据的需求 发展历程&#xff1a; 20世纪70年代末 成长于80年代 第一个数据库系统SDD-1是美国计算机公司(CAA)于1976年-1978年…

浏览器插件官方demo学习(一):基本代码、页面渲染、书签、cookie、Omnibox等

前言 参考&#xff1a;https://github.com/GoogleChrome/chrome-extensions-samples 官方目前只提供了几个基于v3版本的例子&#xff0c;其他例子都是基于v2版本的&#xff08;可能是官方比较忙&#xff0c;没空写例子吧&#xff09;。先从v3版本的例子开始学习&#xff0c;后…

JVM(六) —— 运行时数据区之堆的详细介绍(一)

JVM&#xff08;六&#xff09; —— 运行时数据区之虚拟机栈的详细介绍核心概述堆空间代码演示堆空间划分&#xff08;重要&#xff09;一个Java程序运行起来是一个进程&#xff0c;这个进程对应着一个JVM实例&#xff0c;一个JVM实例对应着一个运行时数据区。而一个运行时数据…