C++ std::reference_wrapper:让引用更强大

news2024/12/23 9:25:47

std::reference_wrapper 的通俗易懂解释

  • 一、简介
  • 二、std::reference_wrapper 的初衷
  • 三、常用示例
    • 3.1、与 `make_pair` 和 `make_tuple` 一起使用
    • 3.2、引用容器
    • 3.3、通过 `std::thread` 按引用传递参数给启动函数
    • 3.4、引用作为类成员
    • 3.5、按引用传递函数对象
    • 3.6、与绑定表达式一起使用
  • 四、总结
  • 五、推荐阅读

一、简介

std::reference_wrapper<T> 是一个可复制、可赋值的对象,它模拟了引用(T&)。它提供了引用不可为空的保证,以及类似指针的灵活性,可以重新绑定到另一个对象。

通常使用 std::ref(或 用于 reference_wrapper<const T>std::cref)来创建 std::reference_wrapper<T>

例如:

template<typename N>
void change(N n) {
  // 如果 n 是 std::reference_wrapper<int>,它会在这里隐式转换为 int&。
  n += 1; 
}

void foo() {
  int x = 10; 

  int& xref = x;
  change(xref); // 按值传递
  // x 仍然是 10
  std::cout << x << "\n"; // 10

  // 显式地按引用传递
  change<int&>(x);
  // x 现在是 11
  std::cout << x << "\n"; // 11

  // 或者使用 std::ref
  change(std::ref(x)); // 按引用传递
  // x 现在是 12
  std::cout << x << "\n"; // 12
}

二、std::reference_wrapper 的初衷

在 C++ 中,尽可能地使用引用而不是指针,因为引用不能为 null,可以明确地表达按引用传递的意图,并且可读性更好,因为没有解引用(*->)的混乱。

但是,引用是对象的顽固别名。这带来了一些挑战,比如上面的例子,函数模板参数类型必须显式指定才能按引用传递。

此外,通常情况下无法获取引用本身的地址,因为根据 C++ 标准,引用不是对象(存储区域)。在 C++ 中,声明对引用的引用、引用的数组以及指向引用的指针都是禁止的。因此,引用类型不满足 STL 容器元素的 Erasable 要求。因此,不能拥有包含引用元素的容器(例如,vectorlist):

std::vector<int&> v; // 错误

此外,引用不能重新绑定到另一个对象。因此,将引用赋值给另一个引用不会赋值引用本身;它会赋值对象:

int x=10, y=20;
int &xref = x, &yref = y;

xref = yref;
// xref 仍然引用 x,现在是 20。

由于引用不能重新绑定,因此将引用作为类成员会很麻烦,因为引用成员会使类不可赋值——默认的复制赋值运算符会被删除。移动语义对于引用成员来说根本没有意义。

std::reference_wrapper 是一个可复制、可赋值的对象,它模拟了引用。与它的名字相反,它不包装引用。它通过封装一个指针T*)并隐式转换为引用(T&)来工作。它不能进行默认构造或用临时对象初始化;因此,它不能为 null 或无效:

std::reference_wrapper<int> nr; // 错误!必须初始化。

std::string str1{"Hello"};
std::string str2{"World"};
auto r1 = std::ref(str1); // OK
auto r2 = std::ref(str2); // OK
// 赋值会重新绑定 reference_wrapper
r2 = r1;  // r2 现在也引用 str1
// 隐式转换为 std::string&
std::string cstr = r2; // cstr 是 "Hello"

// 可以创建 reference_wrapper 的数组
std::reference_wrapper<std::string> arr[] = {str1, str2}; 

auto r2 = std::ref(std::string("Hello")); // 错误!不允许使用临时对象(右值)。

唯一的缺点是,要访问对象的成员(T),必须使用 std::reference_wrapper<T>::get 方法:

std::string str{"Hello"};

auto sref = std::ref(str); 
// 打印 str 的长度
std::cout << sref.get().length() << "\n"; // 5

此外,要为被引用的对象赋值,请使用 get()

sref.get() = "World"; // str 被更改为 "World"
std::cout << str << "\n"; // World

三、常用示例

3.1、与 make_pairmake_tuple 一起使用

std::reference_wrapper 可以用作模板函数(或构造函数)的参数,以避免显式指定模板参数类型。这里一个特殊的例子是 make_pairmake_tuple),它的目的是减少与实例化对(元组)相关的冗长性。比较以下两种方式:

int m=10, n=20;
std::string s{"Hello"};

std::pair<int&, int> p1(m, n);
std::tuple<int&, int, std::string&> t1(m, n, s);
// 与以下方式对比
auto p2 = std::make_pair(std::ref(m), n);
auto t2 = std::make_tuple(std::ref(m), n, std::ref(s));

reference_wrapper 的另一个优点是它不能用临时对象实例化。例如,以下代码会导致未定义行为,因为临时对象的生存期仅延长到构造函数参数超出范围为止:

std::string yell() { return "hey"; }

std::pair<const std::string&, int> p3(yell(), n); // 错误!
// 临时对象 std::string("hey") 这里已经被销毁。
// 访问 p3.first 这里会导致 UB。

以上情况属于悬空引用,可以使用 reference_wrapper 避免:

// 但是,使用 std::ref 是安全的
auto p4 = std::make_pair(std::ref(yell()), n); // 错误!很好

make_pairmake_tuple 在某种程度上与 C++17 的 无关。但是,这些原因仍然适用,现在,使用构造函数:

由于C++17引入了类模板参数推断 (CTAD),所以make_pairmake_tuple的作用已经不再那么重要了。但是,仍然适用于构造函数:

std::pair p5(std::ref(m), n); 

std::tuple t3(std::ref(m), n, std::ref(s));

然而,make_pairmake_tuple与CTAD构造的pairtuple之间存在细微的差别(在大多数情况下这并不重要)。make_pairmake_tuplereference_wrapper<T>转换为引用(T&),而CTAD构造的pairtuple则不是这样。

3.2、引用容器

与引用不同,reference_wrapper 是一个对象,因此满足 STL 容器元素的要求(准确地说是 满足Erasable 要求)。因此,reference_wrapper 可以用作向量元素类型。

reference_wrapper<T> 可以作为指针类型(T*)的安全替代方案,用于存储在向量中:

using namespace std;
vector<reference_wrapper<int>> v;  // OK
int a=10;
v.push_back(std::ref(a));

3.3、通过 std::thread 按引用传递参数给启动函数

可以通过 std::thread(startFunction, args) 在创建新线程时将参数传递给启动函数。这些参数按值从线程创建函数传递,因为 std::thread 构造函数会在将参数传递给启动函数之前复制或移动创建者的参数。

因此,启动函数的引用参数不能绑定到创建者的参数。它只能绑定到 std::thread 创建的临时对象:

void start(int& i) { i += 1; }
void start_const(const int& i) { }

void create() {
 int e = 10; 
 // e 在下面被复制到一个临时对象
 std::thread(start, e).join(); // 错误!不能将临时对象绑定到 int&。
 std::thread(start_const, e).join(); // OK。但实际上是按值传递
}

如果想按引用将参数传递给启动函数,可以通过 std::ref 来实现,如下所示:

void create() {
 int e = 10; 
 std::thread(start, std::ref(e)).join(); // OK。按引用传递
 // e 现在是 11

 std::thread(start_const, std::ref(e)).join(); // 按引用传递
 std::thread(start_const, std::cref(e)).join(); // 按引用传递
}

在上面,std::ref 会生成一个 reference_wrapper<int>,它最终会隐式转换为 int&,从而将 start(int&) 的引用参数绑定到 create() 传递的参数。

3.4、引用作为类成员

将引用作为类成员会带来一些问题,例如,它会使类不可赋值,并且实际上不可移动:

struct W {
 W(int& i):iRef(i) {}
 int& iRef;
};

int u=10, v=20;
W w1(u); 
W w2(v);
w1 = w2; // 错误!隐式删除的复制赋值运算符

通常的做法是避免将引用作为类成员,而是使用指针。

reference_wrapper 提供了二者的最佳组合:

struct W {
 W(int& i):iRef(i) {}
 std::reference_wrapper<int> iRef;
};

W w3(u); 
W w4(v);
w3 = w4; // OK

3.5、按引用传递函数对象

只要 T 是可调用对象,std::reference_wrapper<T> 就可以像函数一样被调用。如果想避免复制大型或有状态的函数对象,这个特性在 STL 算法中特别有用。

此外,T 可以是任何可调用对象——普通函数、lambda 表达式或函数对象。例如:

struct Large {
 bool operator()(int i) const {
  // 过滤和处理
  return true;
 }
 // 大量数据
};

const Large large; // 大量不可变数据和函数对象

std::vector<int> in1; // 输入向量
std::vector<int> in2; // 输入向量

void process() {
 std::vector<int> out;
 // 按引用传递 Large 以避免复制
 std::copy_if(in1.begin(), in1.end(), std::back_inserter(out), std::ref(large));  
 std::copy_if(in2.begin(), in2.end(), std::back_inserter(out), std::ref(large)); 
 // 使用过滤后的 'out' 向量
}

3.6、与绑定表达式一起使用

std::bind 会生成一个可调用包装器,称为绑定表达式,它会将调用转发到包装的可调用对象。绑定表达式可以绑定包装的可调用对象的部分或全部参数。

但是,绑定参数在绑定表达式中会被复制或移动。

void caw(const std::string& quality, const std::string& food) {
 std::cout << "A " << quality << " " << food << "\n";
}

using namespace std::placeholders;  // for _1, _2

std::string donut("donut");
auto donutcaw = std::bind(caw, _1, donut); // donut 被复制
donutcaw("chocolate"); // A chocolate donut

因此,如果想按引用传递绑定参数,需要使用 std::ref(或 std::cref):

std::string muffin("muffin");
auto muffincaw = std::bind(caw, _1, std::ref(muffin)); // muffin 按引用传递
muffincaw("delicious"); // A delicious muffin

四、总结

std::reference_wrapper 是一个强大的工具,它扩展了 C++ 中引用的功能,使其更灵活、更安全。它可以用于:

  • 避免悬空引用: 它不能用临时对象初始化,因此可以有效地防止悬空引用问题。
  • 创建引用容器: std::reference_wrapper 是一个对象,可以作为 STL 容器的元素类型,从而允许创建引用容器。
  • 解决引用作为类成员的难题: std::reference_wrapper 可以作为类成员,使类可赋值,避免了引用成员带来的限制。
  • 按引用传递函数对象: std::reference_wrapper 可以用来按引用传递函数对象,避免了复制大型或有状态的函数对象。
  • 与绑定表达式一起使用: std::reference_wrapper 可以与 std::bind 一起使用,以按引用传递绑定参数。

五、推荐阅读

  • C++ std::reference_wrapper 官方说明(cppreference)。
  • reference_wrapper的提案。

在这里插入图片描述

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

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

相关文章

Megatron-LM源码系列(八): Context Parallel并行

1. Context Parallel并行原理介绍 megatron中的context并行(简称CP)与sequence并行(简称SP)不同点在于&#xff0c;SP只针对Layernorm和Dropout输出的activation在sequence维度上进行切分&#xff0c;CP则是对所有的input输入和所有的输出activation在sequence维度上进行切分&…

「小明赠书活动」第四期《Java开发坑点解析:从根因分析到最佳实践》

⭐️ 赠书 - 《Java开发坑点解析&#xff1a;从根因分析到最佳实践》 这是一本帮助Java开发人员规避常见错误的书。从业务代码开发、项目技术设计、代码安全3个层面剖析150多个常见坑点。 业务代码开发层面&#xff0c;近20个方面的坑&#xff0c;涉及多线程、数据访问、池技术…

【VTKExamples::Utilities】第四期 CameraModifiedEvent

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 公众号:VTK忠粉 前言 本文分享VTK样例CameraModifiedEvent,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO 1. CameraModifi…

521源码-免费音乐源码-最新流媒体在线音乐系统网站源码| 英文版源码| 音乐社区 | 多语言 | 开心版

免费音乐源码 一键自动安装&#xff1a;安装用翻译看提示操作即可 本源码下载地址&#xff1a;最新流媒体在线音乐系统网站源码| 英文版源码| 音乐社区 | 多语言 | 开心版 - 521源码 更多网站源码学习教程&#xff0c;请点击&#x1f449;-521源码-&#x1f448;获取最新资源…

APM2.8如何供电

APM2.8飞控供电有两种&#xff0c; 1.电流计供电&#xff0c; 2.带BEC&#xff08;稳压功能&#xff09;的电调供电 飞控有一个JP1&#xff0c;它是一个供电选择接口&#xff0c;当插入跳线帽时&#xff0c;飞控用带BEC电调供电&#xff0c;当不插入时&#xff0c;用电流计供…

基于springboot的论坛管理系统(含源码+sql+视频导入教程)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 基于springboot的论坛管理系统3拥有两种角色 管理员&#xff1a;用户管理、公告管理、帖子管理、分类管理、留言管理、系统管理等 用户&#xff1a;登录注册、查看发布帖子等 1.1 背景…

深度学习论文: YOLOv10: Real-Time End-to-End Object Detection

深度学习论文: YOLOv10: Real-Time End-to-End Object Detection YOLOv10: Real-Time End-to-End Object Detection PDF: https://arxiv.org/pdf/2405.14458 PyTorch代码: https://github.com/shanglianlm0525/CvPytorch PyTorch代码: https://github.com/shanglianlm0525/PyTo…

如何评价 OpenAI 最新发布支持实时语音对话的模型GPT-4o?OpenAI发完GTP-4o,国内大模型行业还有哪些机会?

文章目录 OpenAI发完GTP-4o&#xff0c;国内大模型行业还有哪些机会&#xff1f;详细了解一下OpenAI最新发布的支持实时语音对话的模型GPT-4o国内大模型如何寻找发展机会&#xff1f;想要发展技术必须要创新与追赶或许应用场景拓展也是一种出路产业生态构建 ChatGPT 问世才 17 …

隆道专属商城 | 助力企业跨平台整合优势资源,解决采购寻源比价难题!

数字化采购时代&#xff0c;企业面临着日益激烈的市场竞争&#xff0c;如何优化资源配置、降低采购成本、提高采购效率成为企业追求的核心目标。当前&#xff0c;网上商城凭借其强大的供应链资源整合能力&#xff0c;为企业内部采购商城的搭建提供了独特的优势&#xff0c;已然…

【Lexus.4】Executive Sedan——Dismantling Follow-up

文章目录 【碰撞测试】前后防撞钢梁偏置碰撞A/B/C柱&#xff0c;边梁抗拉、屈服强度 【底盘】平整度护板&#xff08;发动机&#xff0c;底盘&#xff09;前副车架结构前悬架形式后悬架形式与材质簧下质量 【发动机】【轮上马力】【零部件供应商】 来自2021《懂车大爆炸》——是…

网络风暴:揭秘DDoS攻击的幕后黑手

在数字化时代的浪潮中&#xff0c;网络攻击已成为一种新型的战争手段。其中&#xff0c;分布式拒绝服务攻击&#xff08;DDoS&#xff09;以其强大的破坏力和隐蔽性&#xff0c;成为网络安全领域的一大挑战。DDoS攻击通过发动海量的恶意流量&#xff0c;如同狂风暴雨般席卷目标…

Springboot项目——博客平台

前言&#xff1a;为巩固之前学习的知识&#xff0c;同时锻炼自己的代码能力&#xff0c;项目经验&#xff0c;熟悉前后端交互方式等&#xff0c;特此完成一个博客平台系统。&#xff08;总之&#xff0c;为了学习&#xff0c;为了进步&#xff09; 博客平台&#xff1a;本项目…

干货|图生代码实例整理,让你的代码更高效

前言 “图生代码”。这项新功能允许开发人员直接利用产品设计图一键生成相应的代码&#xff0c;极大地提高了编程效率和研发速度。甚至会未来软件开发可能迎来一场革命性的变革。但图生代码究竟能直到什么程度&#xff1f;本文结合一款图生代码的实例程序整理了一些有代表意义…

如何在 DigitalOcean Droplet 云主机上创建 Ubuntu 服务器

在本文中&#xff0c;你将通过 DigitalOcean 的管理面板创建一个 Ubuntu 服务器&#xff0c;并将其配置为使用你的 SSH 密钥。设置好服务器后&#xff0c;你可以在其上部署应用程序和网站。 本教程是DigitalOcean云课程简介的一部分&#xff0c;它指导用户完成将应用程序安全地…

期望薪资30k字节java2面,A给B转账的同时B给A转账怎么并发量最高

一面 1、自我介绍 2、详细介绍一下自己的做的项目&#xff1f;根据项目提了一些问题 3、hashmap原理 4、B树原理&#xff1f; 5、final禁止重排序原理&#xff1f; 6、设计一个榨汁机类&#xff0c;面向对象怎么设计&#xff1f; 7、get、post区别&#xff0c;使用场景&…

mysql实战——mysql5.7保姆级安装教程

1、上传 上传5.7压缩包到/usr/local目录下 2、解压 cd /usr/local tar -zxvf mysql--5.7.38-linux-glibc2.12-x86_64.tar.gz mv mysql-5.7.38-linux-glibc2.12-x86_64/ mysql 3、创建mysql用户组和用户 groupadd mysql useradd -g mysql mysql 4、创建数据目录data&#xf…

OneForall工具的下载安装和使用(Windows和Linux)

目录 OneForall的介绍 OneForall的下载 OneForall的安装 安装要求 安装步骤&#xff08;git 版&#xff09; 安装&#xff08;kali&#xff09; OneForall的使用命令 在Windows 在Linux&#xff08;kali&#xff09; OneForall的结果说明 免责声明 本文所提供的文字和…

基于Java的高校学生勤工助学优派系统的设计与实现(论文+源码)_kaic

摘 要 高校勤工助学管理系统的出现&#xff0c;让学生的工作更加标准,不仅仅使高校办公室的办公水平以及管理水平大大提高&#xff0c;还优化了勤工助学资金的使用方式方法&#xff0c;完善了资助所需费用的资源配置&#xff0c;可以卓有成效地缩减学校的管理经费。本系统主…

智能SQL代码生成器,开发者的得力助手

&#x1f3e1; 博客首页&#xff1a;IT 派同学 ⛳️ 欢迎关注 &#x1f433; 点赞 &#x1f392; 收藏 ✏️ 留言 &#x1f3a2; 本文由 IT 派同学原创编撰 &#x1f6a7; 系列专栏&#xff1a;《开源专栏》 &#x1f388; 本系列主要输出作者自创的开源项目 &#x1f517; 作品…

B端产品C端化设计,趋势不可挡呀。

一、B端产品和C端产品设计的不同 在设计上&#xff0c;B端&#xff08;Business-to-Business&#xff09;和C端&#xff08;Consumer&#xff09;之间存在一些区别。 用户群体&#xff1a;B端产品的用户是企业或组织&#xff0c;而C端产品的用户是普通消费者。B端产品的用户通…