C++并发:线程函数传参(一)

一、问题

当创建 std::thread 对象时,传递给线程的函数的所有参数都会被复制或移动到新创建的线程的内存空间中。这是为了确保线程的执行不会依赖于父线程可能销毁的栈上变量,从这个机制上看,这是很合理的。

在新的线程的栈上,这些变量都会以右值的方式传递给线程函数,这主要是为了提高传参的性能。但是在某些情况下,这些右值并不能满足线程函数的参数。如果线程函数需要引用参数,直接传递普通变量会因为无法从临时对象(复制或移动产生的)绑定到非 const 引用而失败。比如以下的例子:

#include <iostream>
#include <thread>class BigObject {std::string s = "hello";public:const std::string &getData() const { return s; }void upDateData(const std::string &str) { s = str; }void showInfo() const {std::cout << "addr: " << this << " value: " << s << std::endl;}~BigObject(){};BigObject(){};
};
void update_data_for_BigOb(std::string newString, BigObject &data);
void printInfo(BigObject &ob);void oops_again(std::string w) {BigObject data;printInfo(data);std::thread t(update_data_for_BigOb, w, data);t.join();
}int main() {oops_again("hello_new!"); // 函数调用return 0;
}void update_data_for_BigOb(std::string newString, BigObject &data) {// 修改data.upDateData(newString);printInfo(data);
}
void printInfo(BigObject &ob) { ob.showInfo(); }

这样会编译失败:

g++ parameter1.cxx -o main -std=c++11
In file included from parameter1.cxx:2:
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/thread:280:5: error: attempt to use a deleted function__invoke(_VSTD::move(_VSTD::get<1>(__t)), _VSTD::move(_VSTD::get<_Indices>(__t))...);^
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/thread:291:5: note: in instantiation of function template specialization 'std::__1::__thread_execute<std::__1::unique_ptr<std::__1::__thread_struct>, void (*)(std::__1::basic_string<char>, BigObject &), int, BigObject, 2, 3>' requested here__thread_execute(*__p, _Index());
...

遇到的错误与尝试在 std::thread 构造器中使用不匹配的参数类型有关。错误的根本原因是在创建 std::thread 实例时传递的参数类型与线程函数所期望的参数类型不兼容。因此我们要考虑,传递的参数如何才能正确兼容线程函数期望的参数类型。这正是上面讨论到的一种情况,接着讨论如何优雅的解决这种问题。

二、自动类型转换

在C++中,可以通过构造函数类型转换操作符来实现类对象之间的隐式类型转换:

#include <iostream>class Number {
private:int value;public:// 构造函数Number(int val) : value(val) {}// 类型转换操作符operator int() const {return value;}
};int main() {Number num1 = 5;int result = num1 + 10;  // 隐式类型转换发生在这里std::cout << result << std::endl;  // 输出 15return 0;
}

上文示例中,可以看到 num1 发生了隐式转换(我们暂不考虑强制显式转换),Number 对象转换成了 int 基本类型。这是因为我们定义了类型转换操作符。当我们在 main 函数中执行 num1 + 10 时,由于 10 是一个整数,C++ 编译器将自动调用 operator int() 来将 num1 隐式转换为 int 类型,然后执行加法操作。

三、包装器

以下是 std::ref 的简化版本源码示例:

namespace std {// 定义 ref 类模板template<class T>class reference_wrapper {public:// 构造函数,接受一个对象的引用reference_wrapper(T& ref) : _ref(ref) {}// 拷贝构造函数和赋值运算符被删除,禁止拷贝和赋值reference_wrapper(const reference_wrapper&) = delete;reference_wrapper& operator=(const reference_wrapper&) = delete;// 重载解引用运算符,返回引用对象// 也可以隐式转换为引用对象operator T&() const { return _ref; }private:T& _ref; // 存储引用对象的引用};// ref 函数模板,接受一个对象,并返回一个 reference_wrapper 包装后的对象template<class T>reference_wrapper<T> ref(T& t) {return reference_wrapper<T>(t);}
}

在使用 std::ref 的时候,实际上是将传递的对象包装成了一个 reference_wrapper<T>(t) 对象,如果我们将 问题 中的代码这样改:

...
void oops_again(int w) {
...std::thread t(update_data_for_BigOb, w, std::ref(data));}
...

这将会发生什么?
这个包装器对象(很轻量)将会被移动到新线程的栈中(类中禁止了复制构造函数),然后这个包装器对象被以右值的方式绑定到线程函数的参数。别忘了,这个包装器类内部定义了隐式转换函数

operator T&() const { return _ref; }

这个函数会将包装器对象隐式转化为被包装的对象的引用,而此时,这个被包装的对象正在另一个栈空间中呢,所以它非常适合被绑定到左值引用。当然,这发生在包装器对象尝试以右值的方式被绑定到线程函数的引用类型参数上时

至此,我们可以运行一下修改后的代码,以作验证:

g++ parameter1.cxx -o main -std=c++11
./main
addr: 0x16af47078 value: hello
addr: 0x16af47078 value: hello_new!

可以看到,尽管线程函数传参的路途再曲折,也会顺利将 data 传进去。

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

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

相关文章

通义千问2.5正式发布,能力升级,全面赶超GPT4

简介 在人工智能的大潮中&#xff0c;大模型的竞争愈发激烈。今日&#xff0c;阿里云发布了其最新的通义千问2.5大模型&#xff0c;引起了业界的广泛关注。这款模型不仅在性能上全面赶超了GPT-4&#xff0c;还在多个基准测评中取得了优异的成绩&#xff0c;展现了国产AI技术的…

ARP命令

按照缺省设置&#xff0c;ARP高速缓存中的项目是动态的&#xff0c;每当发送以恶个指定的数据报且高速缓存中不存在当前项目时&#xff0c;ARP便会自动添加该项目。一旦高速缓存的项目被输入&#xff0c;就已经开始走向失效状态。因此&#xff0c;如果ARP高速缓存中的项目很少或…

SPSS之主成分分析

SPSS中主成分分析功能在【分析】--【降维】--【因子分析】中完成&#xff08;在SPSS软件中&#xff0c;主成分分析与因子分析均在【因子分析】模块中完成&#xff09;。 求解主成分通常从分析原始变量的协方差矩阵或相关矩阵着手。 &#xff08;1&#xff09;当变量取值的度量…

【Elasticsearch<五>末篇 ✈️✈️】结合 kibana 实现索引中 IP 地址分布地图可视化

目录 &#x1f44b;前言 &#x1f440;一、ES 地理位置基本了解 &#x1f331;二、IP 地址地图可视化 2.1 创建预处理通道 2.2 创建索引库 2.3 插入一条数据 2.4 观察写入后的数据 2.5 可视化展示 &#x1f604;三、章末 &#x1f44b;前言 继前面了解 Elasticsearch 的安…

酷企秀场景elementUi plus可视化diy

无论网络公司还是政务企业需求的所需的一单可回本的 独立部署集三大功能&#xff1a;电子画册、VR全景、地图秀等功能都可以可视化在线设计 后续免费增加 自定义表单、抽奖活动功能。 源码交付&#xff0c;独立私有化部署&#xff0c;无限多开&#xff0c;可视化设计&#x…

【linux】主分区,扩展分区,逻辑分区,动态分区,引导分区,标准分区

目录 主分区&#xff0c;扩展分区&#xff0c;逻辑分区 主分区和引导分区 主分区&#xff0c;扩展分区&#xff0c;逻辑分区&#xff08;标准分区&#xff09; 硬盘一般划分为一个“主分区”和“扩展分区”&#xff0c;然后在扩展分区上再分成数个逻辑分区。 磁盘主分区扩展…

Redis-2 双写一致性

双写一致性 一.什么是双写一致性&#xff1f; 在数据库中的数据修改后&#xff0c;也要修改缓存中的数据&#xff0c;保证数据库与缓存中保存的数据一致。 二.如何保证双写一致性&#xff1f; 回答该问题一定要结合自己的项目&#xff0c;说明自己的项目是必须强一致性还是…

JavaWeb之过滤器(Filter)与监听器(Listener)

前言 过滤器(Filter) 1.什么是过滤器 2.过滤器的语法格式 3.使用场景 3.1.如何防止用户未登录就执行后续操作 3.2.设置编码方式--统一设置编码 3.3.加密解密(密码的加密和解密) 3.4.非法文字筛选 3.5.下载资源的限制 监听器(Listener) 1.什么是监听器 2.监听器分类…

请介绍下重要的CUDA API

CUDA&#xff08;Compute Unified Device Architecture&#xff09;是由NVIDIA推出的通用并行计算架构&#xff0c;它提供了一系列API供开发者调用&#xff0c;以充分利用GPU进行高性能计算。以下是一些重要的CUDA API&#xff1a; CUDA Runtime API&#xff1a; 这是CUDA编程…

Ci24R1 (SOP8)2.4GHz无线收发一体、双向系统的智能家居芯片

Ci24R1 &#xff08;SOP8&#xff09;工作范围在2.4GHzISM频段&#xff0c;专为低系统应用成本的无线场合设计&#xff0c;集成嵌入式ARQ基带协议引擎的无线收发器芯片。它的工作频率范围为2400MHz-2525MHz&#xff0c;共有126个1MHz带宽的信道。 Ci24R1 &#xff08;SOP8&…

IPFoxy Tips:什么是静态住宅IP?静态ISP代理指南

静态住宅代理&#xff08;也称为静态ISP代理&#xff09;是最流行的代理类型之一。它们也是隐藏您的身份并保持在线匿名的最佳方法之一。您为什么要使用住宅代理而不是仅使用常规代理服务&#xff1f;下面我具体分享。 一、什么是静态住宅代理&#xff1f; 首先&#xff0c;我…

无监督式学习

1.是什么&#xff1f; 无监督式学习与监督式学习**最大的区别就是&#xff1a;**没有事先给定的训练实例&#xff0c;它是自动对输入的示例进行分类或者分群&#xff1b; 优点&#xff1a;不需要标签数据&#xff0c;极大程度上扩大了我们的数据样本&#xff0c;其次不受监督信…

STC8增强型单片机开发day02

逻辑分析仪 什么是逻辑分析仪 逻辑分析仪&#xff08;Logic Analyzer&#xff09;是一种工具&#xff0c;用于分析数字信号&#xff0c;例如控制信号&#xff0c;时钟信号等等。它可以用于调试和验证数字电路、嵌入式系统等等 本人采用的是mini版USB 逻辑分析仪。总共有10个…

刷题《面试经典150题》(第九天)

加油&#xff01; 学习目标&#xff1a;学习内容&#xff1a;学习时间&#xff1a;知识点学习内容&#xff1a;跳跃游戏 II - 力扣&#xff08;LeetCode&#xff09;H 指数 - 力扣&#xff08;LeetCode&#xff09;盛最多水的容器 - 力扣&#xff08;LeetCode&#xff09;矩阵置…

Spring学习笔记

目录 1. Spring有什么优势 1.1 模块化 1.2 轻量级 1.3 方便集成各种优秀框架 1.4 提供了分层开发下的完整技术解决方案 1.5 Java语言编写的开源框架&#xff0c;使用了多种设计模式 2. Spring的第一个程序 2.1 开发环境 2.2 环境搭建 2.3 编码测试 2.4 BeanFactory的UML类图…

pytest教程-42-钩子函数-pytest_runtest_makereport

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 上一小节我们学习了pytest_runtest_teardown钩子函数的使用方法&#xff0c;本小节我们讲解一下pytest_runtest_makereport钩子函数的使用方法。 pytest_runtest_makereport 钩子函数在 pytest 为每个测试生成报…

交易复盘-20240509

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 一支股票 10%的时候可以操作&#xff0c; 90%的时间适合空仓等待 百合花 (4)|[9:25]|[17717万]|1.93 时代万恒…

多线程学习D10 收尾了应该

线程安全集合类概述 重点介绍java.util.concurrent.* 下的线程安全集合类&#xff0c;可以发现它们有规律&#xff0c;里面包含三类关键词&#xff1a;Blocking、CopyOnWrite、Concurrent Blocking 大部分实现基于锁&#xff0c;并提供用来阻塞的方法 CopyOnWrite 之类容器修改…

代码随想录刷题随记31-贪心5

代码随想录刷题随记31-贪心5 435. 无重叠区间 leetcode链接 按照右边界排序&#xff0c;从左向右记录非交叉区间的个数。 此时问题就是要求非交叉区间的最大个数。 这里记录非交叉区间的个数还是有技巧的&#xff0c;如图&#xff1a; 左边界排序可不可以呢&#xff1f; 也是…

多态的概念及运用

多态 概念&#xff1a;多态是指同一种行为具有多个不同的表现形式或形态的能力。在Java中&#xff0c;多态是指一个对象的实际类型可以是其父类或接口类型&#xff0c;但在运行时会根据其实际类型来调用相应的方法。是面向对象的第三大特征&#xff0c;建立于封装和继承之上。…