std::ref和std::cref的使用和原理分析

news2024/11/25 1:03:37

目录

1.用法

2.std::reference_wrapper介绍

3.std::ref原理分析

4.std::cref原理分析

5.总结


1.用法

        它的定义如下:

        std::ref:用于包装按引用传递的值。

        std::cref:用户包装按const引用传递的值。

        C++本身就有引用(&),那为什么C++11又引入了std::ref(或者std::cref)呢?

        这主要是考虑函数式编程(如std::bind或std::thread)在使用时,是对参数直接拷贝,而不是引用。这一点在讲解std::bind是也说的很清楚,bind函数的所有实参(含第1个实参)都是按值传递的。如果有不清楚的地方可参考其定义或下面的博客:

C++中的std::bind深入剖析-CSDN博客

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

        下面举个例子来说明一下它的用法:

#include <functional>
#include <iostream>
 
void f(int& n1, int& n2, const int& n3)
{
    std::cout << "函数中: " << n1 << ' ' << n2 << ' ' << n3 << '\n';
    ++n1; // 增加存储于函数对象的 n1 副本
    ++n2; // 增加 main() 的 n2
    // ++n3; // 编译错误
}
 
int main()
{
    int n1 = 1, n2 = 2, n3 = 3;
    std::function<void()> bound_f = std::bind(f, n1, std::ref(n2), std::cref(n3));
    n1 = 10;
    n2 = 11;
    n3 = 12;
    std::cout << "函数前: " << n1 << ' ' << n2 << ' ' << n3 << '\n';
    bound_f();
    std::cout << "函数后: " << n1 << ' ' << n2 << ' ' << n3 << '\n';
}

输出:

函数前: 10 11 12
函数中: 1 11 12
函数后: 10 12 12

        从上面的例子中可以看到,执行完f,n1的值仍然是1,n2的值已经改变,这说明std::bind使用的是参数的拷贝而不是引用,这也就是为什么C++11要引入std::ref和std::cref的原因了,接下来分析std::ref的实现(std::cref不作分析,因为std::cref和std::ref唯一的差别只是引用变成了const而已)。

2.std::reference_wrapper介绍

        std::reference_wrapper是一个模板类,用于包装引用,使其能够在容器中存储或以引用的形式传递。它提供类似引用的语法,并且可以与标准容器一起使用,因为容器无法直接存储引用。                

        其实使用std::ref时,后台真正起作用的关键类是std::reference_wrapper,它才是真正包装引用的类。它的实现如下:

template <class _Ty>
class reference_wrapper
#if !_HAS_CXX20
    : public _Weak_types<_Ty>
#endif // !_HAS_CXX20
{
public:
    static_assert(is_object_v<_Ty> || is_function_v<_Ty>,
        "reference_wrapper<T> requires T to be an object type or a function type.");

    using type = _Ty;

    template <class _Uty, enable_if_t<conjunction_v<negation<is_same<_Remove_cvref_t<_Uty>, reference_wrapper>>,
                                          _Refwrap_has_ctor_from<_Ty, _Uty>>,
                              int> = 0>
    _CONSTEXPR20 reference_wrapper(_Uty&& _Val) noexcept(noexcept(_Refwrap_ctor_fun<_Ty>(_STD declval<_Uty>()))) {
        _Ty& _Ref = static_cast<_Uty&&>(_Val);
        _Ptr      = _STD addressof(_Ref);
    }

    _CONSTEXPR20 operator _Ty&() const noexcept {
        return *_Ptr;
    }

    _NODISCARD _CONSTEXPR20 _Ty& get() const noexcept {
        return *_Ptr;
    }

private:
    _Ty* _Ptr{};

public:
    template <class... _Types>
    _CONSTEXPR20 auto operator()(_Types&&... _Args) const
        noexcept(noexcept(_STD invoke(*_Ptr, static_cast<_Types&&>(_Args)...))) // strengthened
        -> decltype(_STD invoke(*_Ptr, static_cast<_Types&&>(_Args)...)) {
        return _STD invoke(*_Ptr, static_cast<_Types&&>(_Args)...);
    }
};

从源代码中可以看出以下几点信息:

1)有一个类成员_Ptr,类型为所引用类型的指针,用于存储实际对象的地址

2)std::reference_wrapper的构造函数中的限制条件:

conjunction_v<negation<is_same<_Remove_cvref_t<_Uty>, reference_wrapper>>,
                                          _Refwrap_has_ctor_from<_Ty, _Uty>>

_Remove_cvref_t : 把类型中的引用、const、volatile去掉

is_same: 判断两个数据类型是否一样

negation: 逻辑非

conjunction_v : 逻辑与

_Refwrap_has_ctor_from定义如下:

// CLASS TEMPLATE reference_wrapper
template <class _Ty>
void _Refwrap_ctor_fun(_Identity_t<_Ty&>) noexcept;
template <class _Ty>
void _Refwrap_ctor_fun(_Identity_t<_Ty&&>) = delete;

template <class _Ty, class _Uty, class = void>
struct _Refwrap_has_ctor_from : false_type {};

template <class _Ty, class _Uty>
struct _Refwrap_has_ctor_from<_Ty, _Uty, void_t<decltype(_Refwrap_ctor_fun<_Ty>(_STD declval<_Uty>()))>> : true_type {};

        从template <class _Ty> void _Refwrap_ctor_fun(_Identity_t<_Ty&&>) = delete;这行代码可以看出_Refwrap_has_ctor_from是拒绝右值的,所以我们可以看出std::reference_wrapper的构造是拒绝右值引用的

        接下来在std::reference_wrapper构造函数里面,形参对应的是被持有对象的左值引用类型,其接受ref函数接受的左值引用变量_Ref。通过addressof(_Ref)函数取出该变量的地址,将这个地址信息存入成员变量_Ptr中。

3)重载类型转换标识符  operator _Ty&()和_Ty& get(),这个操作符提供了该对象类型到被包装类型的左值引用类型的类型转换。从而可以让这个对象像未包装前的类型一样去使用。

4)重载函数调用操作符 operator() ,这对应的被包装对象是可调用对象如lambda的数据类型的版本。基本原理和对值类型变量的原理一致

5)std::reference_wrapper与普通引用最大的不同是:该引用可以拷贝或赋值

        从上面的代码中,可以看出来。为了保证引用类型在经过函数模板或者类模板中的值传递过程中可以保持引用信息。这里面采用将传入变量包装成另外一个新的对象,在这个新的对象中持有被包装对象的地址信息。在函数模板和类模板的值传递过程中,对这个新的对象进行值传递,其内部的被包装的对象地址信息可以得到保存。在函数模板或者内模板内部使用这个新的对象的时候,可以通过重载的类型转换函数将被包装变量的地址信息转换还原成相应的引用,对这个引用进行操作。从而达到操作外部变量的作用。

        下面演示将 std::reference_wrapper 作为引用的容器使用,这令使用多重索引访问同一容器称为可能。

#include <algorithm>
#include <functional>
#include <iostream>
#include <list>
#include <numeric>
#include <random>
#include <vector>
 
void println(auto const rem, std::ranges::range auto const& v)
{
    for (std::cout << rem; auto const& e : v)
        std::cout << e << ' ';
    std::cout << '\n';
}
 
int main()
{
    std::list<int> l(10);
    std::iota(l.begin(), l.end(), -4);
 
    // 不能在 list 上用 shuffle(要求随机访问),但能在 vector 上使用它
    std::vector<std::reference_wrapper<int>> v(l.begin(), l.end());
 
    std::ranges::shuffle(v, std::mt19937{std::random_device{}()});
 
    println("list 的内容: ", l);
    println("list 的内容,通过经混洗的 vector 所见: ", v);
 
    std::cout << "倍增初始化式列表中的值...\n";
    std::ranges::for_each(l, [](int& i) { i *= 2; });
 
    println("list 的内容,通过经混洗的 vector 所见: ", v);
}

输出:

list 的内容: -4 -3 -2 -1 0 1 2 3 4 5
list 的内容,通过经混洗的 vector 所见: -1 2 -2 1 5 0 3 -3 -4 4
倍增初始化式列表中的值...
list 的内容,通过经混洗的 vector 所见: -2 4 -4 2 10 0 6 -6 -8 8

3.std::ref原理分析

// FUNCTION TEMPLATES ref AND cref
template <class _Ty>
_NODISCARD _CONSTEXPR20 reference_wrapper<_Ty> ref(_Ty& _Val) noexcept {
    return reference_wrapper<_Ty>(_Val);
}

template <class _Ty>
void ref(const _Ty&&) = delete;

template <class _Ty>
_NODISCARD _CONSTEXPR20 reference_wrapper<_Ty> ref(reference_wrapper<_Ty> _Val) noexcept {
    return _STD ref(_Val.get());
}

有了之前的std::reference_wrapper介绍,std::ref的理解就简单很多了。从源代码中可以看出以下几点信息:

1)std::ref是一个模板函数,返回值是模板类std::reference_wrapper
2)从第二个函数可以看到,std::ref不允许传递右值引用参数,即无法包装右值引用传递的值
std::ref的传入参数可以是一个普通的引用,也可以是另外一个std::reference_wrapper对象

示例如下:

#include <iostream>
#include <functional>

void func(int& value) {
    value *= 2;
}

int main() {
    int number = 42;
    auto refNumber = std::ref(number);

    func(refNumber);  // 使用可修改的引用作为参数

    std::cout << "func Value: " << number << std::endl;

    return 0;
}

        调用std::ref返回一个类型为std::reference_wrapper的refNumber, 调用func前隐式转换成这样int&类型的参数, 然后就能顺利的改变 refNumber 的值了。

4.std::cref原理分析

template <class _Ty>
_NODISCARD _CONSTEXPR20 reference_wrapper<const _Ty> cref(const _Ty& _Val) noexcept {
    return reference_wrapper<const _Ty>(_Val);
}

template <class _Ty>
void cref(const _Ty&&) = delete;

template <class _Ty>
_NODISCARD _CONSTEXPR20 reference_wrapper<const _Ty> cref(reference_wrapper<_Ty> _Val) noexcept {
    return _STD cref(_Val.get());
}

        std::cref只是在std::ref基础上加了一个const,表示它返回的这个引用不能修改值。其它都一样。可以在需要引用的地方使用。这在函数参数传递中特别有用,因为它允许我们在不进行拷贝的情况下传递常量对象,同时保持引用的语义。

        示例如下:

#include <iostream>
#include <functional>

void printValue(const int& value) {
    std::cout << "Value: " << value << std::endl;
}

int main() {
    int number = 42;
    auto crefNumber = std::cref(number);

    printValue(crefNumber);  // 使用常量引用传递参数

    return 0;
}

5.总结

        总的来说,std::ref 和 std::cref 是用于创建引用包装器的有用工具,但它们通常需要与其他技术(如 std::bind 或 lambda 表达式)结合使用,让它展现出和普通引用类似的效果,以便与 C++ 的算法库等接口兼容。

std::reference_wrapper - cppreference.com

std::ref, std::cref - cppreference.com

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

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

相关文章

使用 Python 中的 TensorFlow 检测垃圾短信

前言 系列专栏&#xff1a;机器学习&#xff1a;高级应用与实践【项目实战100】【2024】✨︎ 在本专栏中不仅包含一些适合初学者的最新机器学习项目&#xff0c;每个项目都处理一组不同的问题&#xff0c;包括监督和无监督学习、分类、回归和聚类&#xff0c;而且涉及创建深度学…

【鸿蒙开发】第二十四章 IPC与RPC进程间通讯服务

1 IPC与RPC通信概述 IPC&#xff08;Inter-Process Communication&#xff09;与RPC&#xff08;Remote Procedure Call&#xff09;用于实现跨进程通信&#xff0c;不同的是前者使用Binder驱动&#xff0c;用于设备内的跨进程通信&#xff0c;后者使用软总线驱动&#xff0c;…

一个基于servlet的MVC项目-登录验证

一、MVC的概念 MVC是Model、View、Controller的缩写&#xff0c;分别代表 Web 应用程序中的3种职责1 模型:用于存储数据以及处理用户请求的业务逻辑。 2视图:向控制器提交数据&#xff0c;显示模型中的数据。 3控制器:根据视图提出的请求&#xff0c;判断将请求和数据交给哪个…

Linux下网络命令

目录 需求1-查看本机是否存在22端口解法1解法2解法3 需求2-查看其他主机是否存在22端口解法1解法2解法3 需求3-查看TCP连接解法1/2 需求4-统计80端口tcp连接次数解法 需求5-查看总体网络速度解法 需求6-查看进程流量解法 需求7-dns解法 需求8-traceroute到baidu解法 需求9-查看…

git仓库使用

git仓库是会限制空间大小限制的 git网络库的容量限制_github仓库大小限制-CSDN博客 git是用于管理github的工具 电脑左下角搜索git打开GitBash.exe 进入到要下载到本地的目录 下载到本地的文件不要更改&#xff01; 如果要使用请务必把文件复制到别的空间去再在这个别的空间…

centos7中查询Nacos的安装路径和配置信息如何查找?

在 CentOS 7 上查询 Nacos 的安装路径和配置信息通常涉及几个步骤。这些步骤主要依赖于你是如何安装 Nacos 的&#xff08;比如使用压缩包还是 Docker 等方式&#xff09;。下面是一些通用的方法来帮助你找到 Nacos 的安装路径和配置信息&#xff1a; 1. 查找 Nacos 的安装路径…

智能自助终端主板RK3288/RK3568在酒店前台自助机方案的应用,支持鸿蒙,支持免费定制

酒店前台自助机解决方案是一款基于自助服务终端&#xff0c;能够让客人通过简单的操作完成入住登记/退房的解决方案&#xff0c;大幅提高酒店的工作效率&#xff0c;提升客人体验&#xff0c;降低人力成本。 该方案解决了以下传统前台登记入住方式的痛点&#xff1a; 1、人流量…

elasticsearch 动态映射

文章目录 动态映射动态映射的弊端静态映射实战&#xff1a;映射创建后还可以更新吗 动态映射 动态映射的核心是在自动检测字段类型后添加新字段 哪些字段类型支持动态检测呢&#xff1f; 答&#xff1a;boolean类型、float类型、long类型、Object类型、Array类型、date类型、…

Hive-表设计优化

Hive-表设计优化 1.Hive查询基本原理 Hive的设计思想是通过元数据解析描述将HDFS上的文件映射成表。 基本的查询原理是当用户通过HQL语句对Hive中的表进行复杂数据处理和计算时&#xff0c;默认将其转换为分布式计算MapReduce程序对HDFS中的数据进行读取处理的过程。 当执行…

黑马甄选离线数仓项目day02(数据采集)

datax介绍 官网&#xff1a; https://github.com/alibaba/DataX/blob/master/introduction.md DataX 是阿里云 DataWorks数据集成 的开源版本&#xff0c;在阿里巴巴集团内被广泛使用的离线数据同步工具/平台。 DataX 实现了包括 MySQL、Oracle、OceanBase、SqlServer、Postgre…

48-Qt控件详解:Buttons Containers2

一 Group Box:组合框 #include "widget.h"#include<QGroupBox> #include<QRadioButton> #include<QPushButton> #include<QVBoxLayout>//可以在水平方向和垂直方向进行排列的控件&#xff0c;QHBoxLayout/QVBoxLayout #include <QGridLa…

Adobe Media Encoder ME v24.3.0 解锁版 (视频和音频编码渲染工具)

Adobe系列软件安装目录 一、Adobe Photoshop PS 25.6.0 解锁版 (最流行的图像设计软件) 二、Adobe Media Encoder ME v24.3.0 解锁版 (视频和音频编码渲染工具) 三、Adobe Premiere Pro v24.3.0 解锁版 (领先的视频编辑软件) 四、Adobe After Effects AE v24.3.0 解锁版 (视…

新增柱线组合图、象限图,新增钉钉、飞书、企业微信客户端免密登录,DataEase开源数据可视化分析工具v2.6.0发布

2024年5月13日&#xff0c;人人可用的开源数据可视化分析工具DataEase正式发布v2.6.0版本。 这一版本的功能升级包括&#xff1a;图表方面&#xff0c;新增了柱线组合图、象限图&#xff1b;仪表板方面&#xff0c;支持批量拖拽字段&#xff0c;外部参数新增支持配置过滤组件&…

云原生基础设施和操作系统分论坛 03-在Kubernetes上运行Apache Spark进行大规模数据处理的实践【数据分析】

https://spark.apache.org/视频观看&#xff1a;https://www.bilibili.com/video/BV17J4m1n7Gv/?spm_id_from333.999.0.0 简介 Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎。Spark是UC Berkeley AMP lab (加州大学伯克利分校的AMP实验室)所开源的类Hadoop…

/proc/modules文件

/proc/modules文件中列出了内核加载的所有模块的信息&#xff0c;与使用lsmod命令类似。 第一列&#xff1a;模块名称 第二列&#xff1a;模块使用的内存大小&#xff0c;单位是bytes 第三列&#xff1a;模块被load的次数 第四列&#xff1a;是否有其他模块依赖此模块&#…

git使用及github

文章目录 操作命令基本组成框架在开发中git分支的重要性 github的使用将本地仓库关联到远程仓库将远程仓库关联到本地和拉取指定分支、切换远程分支提交本地仓库到远程仓库修改分支名称 保存当前工作切换分支将别的分支修改转移到自己的分支远程删除分支后本地git branch -a依然…

基于NTP服务器获取网络时间的实现

文章目录 1 NTP1.1 简介1.2 包结构1.3 UNIX 时间戳和NTP时间戳 2 代码实现2.1 实现步骤2.2 完整代码 3 结果 在某些场景下&#xff0c;单片机需要通过网络获取准确的时间进行数据同步&#xff0c;例如日志记录、定时任务等。然而&#xff0c;单片机本身无法直接获得准确的标准时…

Vue+springboot的批量删除功能

vue前台 <div style"margin-bottom: 10px"><el-button type"primary" plain click"handleAdd">新增</el-button><el-button click"delBatch" type"danger" plain style"margin-left: 5px"…

自学新标日第十三课(完结)

第十三课 单词 单词假名声调词义荷物にもつ1包裹はがきはがき0明信片切手きって0邮票引き出しひきだし0抽屉アルバムあるばむ0相册タバコたばこ0烟草漫画まんが&#xff10;ガレージ&#xff12;车库修理しゅうり&#xff11;生ビールなまビール3生啤焼き鳥やきとり0烤鸡肉串…

I. Integer Reaction

Problem - I - Codeforces 看到最小值最大值&#xff0c;二分答案。 思路&#xff1a;每次二分时开两个集合&#xff0c;分别表示 0 0 0颜色和 1 1 1颜色。如果是 c c c颜色&#xff0c;先将值存入 c c c颜色&#xff0c;之后在 ! c !c !c颜色中找大于等于 m i d − a mid - a…