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;  // 输出 15
    return 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.mfbz.cn/a/609563.html

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

相关文章

基于Django图像识别系统毕业设计(付源码)

前言&#xff1a;Django是一个由Python编写的具有完整架站能力的开源Web框架&#xff0c;Django本身基于MVC模型&#xff0c;即Model&#xff08;模型&#xff09;View&#xff08;视图&#xff09; Controller&#xff08;控制器&#xff09;设计模式&#xff0c;因此天然具有…

零售数据分析之连带销售分析怎么做

连带销售是指顾客在购买某款产品后&#xff0c;通常会顺手也买上另一款产品。这种情况在超市零售中屡见不鲜&#xff0c;因此通常来说在做超市零售数据分析时&#xff0c;都需要做一个详尽的连带销售分析。那么做零售数据分析中的连带销售分析&#xff0c;要计算分析哪些指标&a…

MBR与GPT分区表

文章目录 MBR分区表MBR分区表结构MBR分区表项查看U盘的分区表信息查看系统中所有磁盘的分区类型获取分区表信息 GPT分区表保护性MBRGPT分区表头格式GPT分区表项格式分区类型分区属性分区表项内容 MBR分区表 CHS &#xff1a;磁头&#xff08;Heads&#xff09;、柱面(Cylinder…

AH8651-220V转3.3V低成本方案

本篇文章将介绍一种220V转3.3V低成本方案&#xff0c;该方案采用AH8651芯片&#xff0c;无需外接电感&#xff0c;具有高效率的智能控制、宽广的交流输入范围、内置过流保护、欠压保护和过热自动关断等功能。AH8651可以通过SEL引脚选择输出电压&#xff0c;启动时通过内部高压电…

【连连国际注册/登录安全分析报告】

连连国际注册/登录安全分析报告 前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨…

Numpy的数组操作

文章目录 数组的创建创建全0的二维数组a(3,3)全1的二维数组b&#xff08;3,4&#xff09;随机数二维数数组c&#xff08;2,3&#xff09;效果截图 数组的属性查看b数组的维度查看b数组元素的个数效果截图 数组的维度操作将数组c的行变列&#xff0c;返回最后一个元素返回数组c第…

vue3打开页面后文本框自动获得焦点

字符串写法 <script setup> import { ref, onMounted } from vue import ./index.cssconst input ref(null)onMounted(() > {input.value.focus() }) </script><template><div class"m-home-wrap"><input ref"input" />…

用Rust打印杨辉三角

一、杨辉三角是什么&#xff1f; 杨辉三角是一个著名的数学图形&#xff0c;它展示了二项式系数的排列方式。 杨辉三角是一种将二项式系数以三角形阵列排列的数学图形&#xff0c;具有丰富的历史和数学意义。 杨辉三角的历史起源可以追溯到中国南宋时期&#xff0c;由数学家杨辉…

「 网络安全常用术语解读 」漏洞利用预测评分系统EPSS详解

1. 概览 EPSS&#xff08;Exploit Prediction Scoring System&#xff0c;漏洞利用预测评分系统&#xff09; 提供了一种全新的高效、数据驱动的漏洞管理功能。EPSS是一项数据驱动的工作&#xff0c;使用来自 CVE 的当前威胁信息和现实世界的漏洞数据。 EPSS 模型产生 0 到 1&…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-15-GPIO中断控制实验

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

大语言模型LLM原理篇

大模型席卷全球&#xff0c;彷佛得模型者得天下。对于IT行业来说&#xff0c;以后可能没有各种软件了&#xff0c;只有各种各样的智体&#xff08;Agent&#xff09;调用各种各样的API。在这种大势下&#xff0c;笔者也阅读了很多大模型相关的资料&#xff0c;和很多新手一样&a…

电脑ip地址设置成什么比较好

随着信息技术的快速发展&#xff0c;IP地址已成为电脑在网络世界中的“身份证”。它不仅是电脑在网络中进行通信的基础&#xff0c;也直接关系到网络连接的稳定性、安全性和效率。然而&#xff0c;面对众多IP地址设置选项&#xff0c;许多用户可能会感到困惑。那么&#xff0c;…

图形网络的自适应扩散 笔记

1 Title Adaptive Diffusion in Graph Neural Networks&#xff08;Jialin Zhao、Yuxiao Dong、Ming Ding、Evgeny Kharlamov、Jie Tang&#xff09;【NIPS 2021】 2 Conclusion The neighborhood size in GDC is manually tuned for each graph by conductin…

docker-compose集成elasticsearch7.17.14+kibana7.17.14

1.docker和compose版本必须要高 2.准备ik分词器&#xff08;elasticsearch-analysis-ik-7.17.14&#xff09;&#xff0c;下面会用到 https://github.com/infinilabs/analysis-ik/releases?page2 3.配置es-compose.yml&#xff08;切记映射容器内路径不能更改,es和kibana服务…

每日OJ题_记忆化搜索⑤_力扣329. 矩阵中的最长递增路径

目录 力扣329. 矩阵中的最长递增路径 解析代码1_爆搜递归&#xff08;超时&#xff09; 解析代码2_记忆化搜索 力扣329. 矩阵中的最长递增路径 329. 矩阵中的最长递增路径 难度 困难 给定一个 m x n 整数矩阵 matrix &#xff0c;找出其中 最长递增路径 的长度。 对于每…

【LeetCode算法】389. 找不同

提示&#xff1a;此文章仅作为本人记录日常学习使用&#xff0c;若有存在错误或者不严谨得地方欢迎指正。 文章目录 一、题目二、思路三、解决方案 一、题目 给定两个字符串 s 和 t &#xff0c;它们只包含小写字母。字符串 t 由字符串 s 随机重排&#xff0c;然后在随机位置添…

移动端自动化测试工具 Appium 之 main 启动

文章目录 一、背景二、生成xml文件2.1、创建xml方法2.2、执行主类MainTest2.3、自动生成的xml2.4、工程目录2.5、执行结果 三、命令行执行appium服务四、主方法启动类五、集成Jenkins六、总结 一、背景 Jenkins 做集成测试是不错的工具&#xff0c;那么UI自动化是否可以&#…

macOS12安装 php7.1和apache

1. 安装php 7.1 macOS12不再自带php brew tap shivammathur/php 查看可安装版本 brew search php 安装指定版本&#xff08;禅道适用PHP运行环境(7.0/7.1/7.2版本)&#xff09; brew install php7.1 环境配置 vim ~/.zshrc export PATH"/usr/local/opt/php7.1/bin:…

uni-app 滚动到指定位置

方法1&#xff1a;使用标签&#xff0c;可以将页面横向&#xff08;或纵向&#xff09;滚动到指定位置 无法滚动 将代码放在setTimeout&#xff0c;nextTick里执行 <!-- 左边 --><scroll-view show-scrollbar"false" scroll-y"true" class"…

Flutter笔记:Widgets Easier组件库(13)- 使用底部弹窗

Flutter笔记 Widgets Easier组件库&#xff08;13&#xff09;使用底部弹窗 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this …
最新文章