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

目录

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

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

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

相关文章

笔记-跨域方式实现原理

websocket Websocket是HTML5的一个持久化的协议&#xff0c;它实现了浏览器与服务器的全双工通信&#xff0c;同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议&#xff0c;都基于 TCP 协议。但是 WebSocket 是一种双向通信协议&#xff0c;在建立连接之后&#xff…

《系统架构设计师教程(第2版)》第11章-未来信息综合技术-01-信息物理系统(CPS)技术概述

文章目录 1. 信息物理系统&#xff08;CPS&#xff09;1.1 来源1.2 定义1.3 本质 2. CPS的实现2.1 CPS 的体系架构2.1.1 单元级2.1.2 系统级2.1.3 SoS级 2.2 CPS 的技术体系2.2.1 感知和自动控制1&#xff09;智能感知技术2&#xff09;虚实融合控制技术 2.2.2 工业软件2.2.3 工…

WPF使用ItemsControl显示Object的所有属性值

对于上位机开发&#xff0c;我们有时候有这样的需求&#xff1a;如何显示所有的IO点位&#xff1f;比如有10个IO点位&#xff0c;那我们要写10个TextBlock去绑定这10个点位的属性&#xff08;本文暂时不考虑显示的样式&#xff0c;当然也可以考虑&#xff09;&#xff0c;当点位…

绝地求生PUBG新老艾伦格有什么差别 老艾伦格什么时候回归

复古风格的艾伦格原始地图携带着那些标志性的记忆符号华丽回归&#xff0c;邀请您沉浸于往昔的每一处细节探索中。我们不仅还原了游戏诞生的起点&#xff0c;还在其中巧妙融入现代游戏元素&#xff0c;构筑一座连接昔日与今朝的桥梁&#xff0c;完美融合了经典与创新的游戏体验…

【AI大模型应用开发】LATS:比ToT和ReAct更强大的大模型思维框架

大家好&#xff0c;我是 同学小张&#xff0c;持续学习C进阶知识和AI大模型应用实战案例&#xff0c;持续分享&#xff0c;欢迎大家点赞关注&#xff0c;共同学习和进步。 我们在大模型中常听说CoT&#xff08;思维链&#xff09;、ToT&#xff08;思维树&#xff09;&#xff…

深入理解C#中的IO操作:Path类的详解

文章目录 前言一、Path类的概述二、Path类的主要方法2.1 Path.GetFullPath(string relativePath)2.2 Path.GetDirectoryName(string path)2.3 Path.GetFileName(string path)2.4 Path.GetFileNameWithoutExtension(string path)2.5 Path.GetExtension(string path)2.6 Path.Com…

JUC下的Future 详解

java.util.concurrent.Future 是Java并发编程中一个重要的接口&#xff0c;它代表一个异步计算的结果。当你提交一个任务到执行器&#xff08;如ExecutorService的submit方法&#xff09;&#xff0c;它会返回一个Future对象。这个对象允许你查询任务是否完成、取消任务、获取任…

【2024年5月备考新增】】 考前篇(4)《官方平台 - 考生模拟练习平台常用操作(三)》

9 如何绘制七格图 第一步:选择图形元件“网络计划”中的七格图元件,在绘图区点击 第二步:批量复制(先选中元件,按Ctrl+C, 再 Ctrl+V) 七格形状,有多少活动复制多少个 选中多个图形后,可点左上角的对齐工具进行对齐; 第三步:逐个标注每个活动的参数,使用箭线连接每个…

vscode无法连接 , .vscode-server版本问题

vscode无法连接 &#xff0c; .vscode-server版本问题 解决办法 &#xff1a; 查看自己的版本号 2. 两边vscode版本号需要一致 找一台vscode可以远程连接的&#xff0c; 将它的.vscode-server/bin/b06ae3b2d2dbfe28bca3134cc6be65935cdfea6a 传到 远程服务器上 或者 本地的…

2024统计建模成品论文39页(附带完整数据集和代码)

2024统计建模成品论文完整版一等奖论文【1.5w字全网最佳】2024统计建模大赛高质量成品论文39页配套完整代码运行全套数据集https://www.jdmm.cc/file/2710661/

Vue的学习 —— <vue指令>

目录 前言 正文 内容渲染指令 内容渲染指令的使用方法 v-text v-html 属性绑定指令 双向数据绑定指令 事件绑定指令 条件渲染指令 循环列表渲染指令 侦听器 前言 在完成Vue开发环境的搭建后&#xff0c;若想将Vue应用于实际项目&#xff0c;首要任务是学习Vue的基…

内存屏障 - LINUX KERNEL MEMORY BARRIERS 上 与 下

内存屏障&#xff08;Memory Barrier&#xff09;是在计算机体系结构中使用的一种同步机制&#xff0c;用于确保在多线程或多核处理器环境中&#xff0c;对共享内存的操作按照预期顺序进行。它们通过强制在特定点执行一些指令来规定内存访问的顺序&#xff0c;并防止内存乱序执…

干货分享:AI知识库-从认识到搭建

随着知识库的出现&#xff0c;人工智能也逐渐加入进来&#xff0c;形成了“AI知识库”。也许将AI和知识库拆开&#xff0c;你能理解是什么意思&#xff0c;但是当两个词结合在一起时&#xff0c;你又真的能理解它是做什么的吗&#xff1f;这就是今天我们要来聊的话题&#xff0…

未来互联网:Web3的技术革新之路

引言 随着技术的不断发展和社会的日益数字化&#xff0c;互联网作为信息交流和社交媒介的重要平台已经成为我们生活中不可或缺的一部分。然而&#xff0c;传统的互联网架构在数据安全、隐私保护和去中心化等方面存在着诸多挑战。为了解决这些问题&#xff0c;Web3技术应运而生…

【PB案例学习笔记】-01创建应用、窗口与控件

写在前面 这是PB案例学习笔记系列文章的第一篇&#xff0c;也是最基础的一篇。后续文章中【创建程序基本框架】部分操作都跟这篇文章一样&#xff0c; 将不再重复。该系列文章是针对具有一定PB基础的读者&#xff0c;通过一个个由浅入深的编程实战案例学习&#xff0c;提高编…

uniapp编译H5解决ios的border-radius失效问题,以及ios满屏显示不全的问题

1.解决方案 .card-itemA {width: 650rpx;height: 326rpx;box-shadow: 0rpx 0rpx 30rpx 14rpx rgba(236, 235, 236, 0.25);background: linear-gradient(180deg, #FFFFFF 0%, rgba(255, 255, 255, 0) 100%);border-radius: 60rpx;overflow: hidden;// 兼容ios的圆角问题transfor…

Centos7 配置 DNS服务器

Centos 7 配置DNS服务器 环境描述&#xff1a; 一台服务器和一台用于测试的客户机 服务器IP&#xff1a;192.168.200.132 客户机IP&#xff1a;192.168.200.143 服务器配置 yum install bind bind-utils -y #安装软件包vim /etc/named.conf //编辑named主配置文件listen-on p…

d18(169-174)-勇敢开始Java,咖啡拯救人生

目录 特殊文件 .properties 属性文件 读取属性文件 写出属性文件 .xml XML文件 读取XML文件 ​编辑 写出XML文件 约束XML文件 日志技术 Logback 日志级别 特殊文件 .properties 属性文件 每行都是一个键值对 键不能重复 文件后缀一般是.properties 读取属性文件 …

LeetCode 力扣题目:买卖股票的最佳时机 IV

❤️❤️❤️ 欢迎来到我的博客。希望您能在这里找到既有价值又有趣的内容&#xff0c;和我一起探索、学习和成长。欢迎评论区畅所欲言、享受知识的乐趣&#xff01; 推荐&#xff1a;数据分析螺丝钉的首页 格物致知 终身学习 期待您的关注 导航&#xff1a; LeetCode解锁100…

华为手机恢复出厂设置后怎么还原数据?该如何预防数据丢失?

华为手机恢复出厂设置是将手机恢复到出厂时的初始状态&#xff0c;同时会删除所有用户数据和个人设置。如果不做任何预防措施&#xff0c;在恢复出厂设置后&#xff0c;您将丢失手机上的所有数据。那华为手机恢复出厂设置后怎么还原数据呢&#xff1f;以下是关于如何在华为手机…