首页 > 编程语言 >C++11中std::ref()与&

C++11中std::ref()与&

时间:2023-09-24 16:45:43浏览次数:42  
标签:11 std reference int wrapper 引用 ref

C++11中std::ref()与&

引言

最近看到一个多线程代码如下:

typedef unsigned long long ULL;

void accumulator_function(const std::vector<int> &v, ULL &acm,
    unsigned int beginIndex, unsigned int endIndex)
{
    acm = 0;
    for (unsigned int i = beginIndex; i < endIndex; ++i)
    {
        acm += v[i];
    }
}

int main()
{
    ULL acm1 = 0;
    ULL acm2 = 0;
    std::vector<int> v = { 1,2,3,4,5,6,7,8,9,10 };

    std::thread t1(accumulator_function, std::ref(v),
        std::ref(acm1), 0, v.size() / 2);

    std::thread t2(accumulator_function2, std::ref(v),
        std::ref(acm2), v.size() / 2, v.size());

    t1.join();
    t2.join();

    std::cout << acm1 << "+" << acm2 << "=" << acm1 + acm2 << std::endl;
    return 0;
}

其中创建线程的部分使用了std::thread t1(accumulator_function2, std::ref(v), std::ref(acm1), 0, v.size() / 2);,对应的函数实现为void accumulator_function(const std::vector<int> &v, ULL &acm, unsigned int beginIndex, unsigned int endIndex),其中的std::ref()之前在C++ Primer中看过,感觉应该和&差不多吧,但是既然这样为什么仍然需要用ref转换成引用形式呢?立刻把std::ref()全部删了,重新运行,结果报错了......有点意思,可以探究一波。

探究过程

引用的例子首先列举一个:

void fun(vector<int>& a, int i) {
    a[i] = 20;
}
int main() {
    std::vector<int> v = {1, 1, 1, 1, 1, 1};
    for (auto x : v)
        cout << x << ' ';
    cout << endl;
    fun(v, 3);  // 等同于fun(std::ref(v), 3);
    for (auto x : v)
        cout << x << ' ';
}
// Output:
// 1 1 1 1 1 1
// 1 1 1 20 1 1

如果是普通引用的话,只需要调用fun(v, 3)就行了,为什么在例子中使用了fun(std::ref(v),..)这种形式呢?说明std::ref()&是不一样的么?写个例子看一下:

#include <iostream>
#include <type_traits>
int main() {
    int x = 5;
    std::cout << std::boolalpha << std::is_same<int&, decltype(std::ref(x))>::value;
    return 0;
}

输出答案果然是false!那么如果std::ref()返回的不是对象的引用,返回的是什么?查一下手册可以发现:函数模板 refcref 是生成std::reference_wrapper类型对象的辅助函数,它们用模板实参推导确定结果的模板实参。所以std::ref()返回的实际上是一个reference_wrapper而不是T&,可以从一个指向不能拷贝的类型的对象的引用生成一个可拷贝的对象。std::reference_wrapper 是包装引用于可复制、可赋值对象的类模板。它常用作将引用存储入无法正常保有引用的标准容器(类似 std::vector )的机制。

修改一下上面的例子,看看结果:

#include <iostream>
#include <type_traits>
int main() {
    int x = 5;
    std::cout << std::boolalpha << std::is_same<int&, decltype(std::ref(x).get())>::value;
    return 0;
}

变为true了。reference_wrapper&并不一样,但是利用std::reference_wrapper 对象的get()方法返回其引用,发现就是&类型。但是为什么在多线程那个例子中要使用std::ref()呢?

原因是C++的函数式编程(如std::bind)在使用时是对参数直接拷贝,而不是引用。具体可以参照这一句话:std::reference_wrapper 亦用于按引用传递对象给 std::bindstd::thread 的构造函数。

还是通过代码理解一下:

#include <functional>
#include <iostream>

void f(int& n1, int& n2, const int& n3) {
    std::cout << "In function: " << n1 << ' ' << n2 << ' ' << n3 << '\n';
    ++n1;  // increments the copy of n1 stored in the function object
    ++n2;  // increments the main()'s n2
    // ++n3;  // compile errors
}

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 = 4;
    n2 = 5;
    n3 = 6;
    std::cout << "Before function: " << n1 << ' ' << n2 << ' ' << n3 << '\n';
    bound_f();
    std::cout << "After function: " << n1 << ' ' << n2 << ' ' << n3 << '\n';
}
/*Output:
Before function: 4 5 6
In function: 1 5 6
After function: 4 6 6
*/

上述代码在执行std::bind后,在函数f()中n1的值仍然是1,n2和n3改成了修改的值。说明std::bind使用的是参数的拷贝而不是引用。C++11的设计者认为std::bind默认应该采用拷贝,如果使用者有需求,加上std::ref()即可。同理std::thread也是这样。

结论

std::ref只是尝试模拟引用传递,并不能真正变成引用,在非模板情况下,std::ref根本没法实现引用传递,只有模板自动推导类型时,ref能用包装类型std::reference_wrapper来代替原本会被识别的值类型,而std::reference_wrapper能隐式转换为被引用的值的引用类型,但是其本身并不能被用作&类型,可以通过其get()方法获得其内部对象的引用。

而回到刚开始的那个多线程代码,std::thread的方法传递引用的时候,我们希望使用的是参数的引用,而不是浅拷贝,所以必须用ref来进行引用传递。

标签:11,std,reference,int,wrapper,引用,ref
From: https://www.cnblogs.com/3to4/p/17726164.html

相关文章

  • 2023-2024-1 20211211《信息安全系统设计与实现(上)》第10章学习笔记
    内容目录一、程序设计语言与shell脚本(1)一门程序设计语言有哪些必备要素和技能(2)这些要素和技能在shell脚本中如何呈现二、sh脚本三、sh脚本与C程序四、命令行参数五、sh变量六、sh中的引号七、sh命令(1)内置命令(2)linux命令八、sh控制语句(1)if-else-fi(2)if-elif-e......
  • 2023-2024-1 20211327 信息安全系统设计与实现 学习笔记3(必做)
    学习笔记3程序设计语言必备要素和技能shell脚本实践过程截图程序设计语言必备要素和技能1.语法和语义:了解语言的基本语法规则和语义,包括如何定义变量、数据类型、运算符、控制结构等。这是编写有效代码的基础。2.数据结构和算法:掌握数据结构(如数组、链表、栈、队列)和基......
  • 《看了受制了》第二十四天,7道题,合计114道题
    2023年9月23日今天周六,尽力做了做,虽然Acwing没能AK。。没读懂题。Acwing5152简单输出题目理解基础语法代码实现#include<iostream>#include<algorithm>#include<unordered_map>#include<cstring>#include<cstdio>#include<vector>#include<queue>#i......
  • 11.变量的作用域
    变量和函数的作用域:'usestrict'functionf(a){vara=1;a=a+1;}a=111111111111111;在Javascript中,var定义变量是有作用域的在函数体中声明,在函数体外是不能用的内部函数可以访问外部函数,反之不行functionf(x){//相当......
  • 20211325 2023-2024-1 《信息安全系统设计与实现(上)》第三周学习笔记
     202113252023-2024-1《信息安全系统设计与实现(上)》第三周学习笔记一、任务要求自学教材第10章,提交学习笔记(10分)大家学习过Python,C,Java等语言,总结一下一门程序设计语言有哪些必备的要素和技能?这些要素和技能在shell脚本中是如果呈现出来的?,评分标准如下1.知识点......
  • CodeForces 1149D Abandoning Roads
    洛谷传送门CF传送门考虑一条\(1\toi\)的路径是否在最小生成树上。称边权为\(a\)的边为轻边,边权为\(b\)的边为重边。轻边若不成环则一定在最小生成树上,因此先把轻边合并,这样形成了若干连通块。那么如果两点在一个连通块,它们只能通过轻边互达。同时,因为是树上路径,所......
  • win11任务栏变成透明的教程
    win11任务栏变成透明的教程其实win11原版的任务栏以纯色为主,并且没有任何的透明效果,让桌面壁纸无法完美展示出来,非常难看,因此我们可以通过第三方软件的方式来将它透明化,下面就一起来看一下具体的教程吧。win11任务栏怎么透明方法一:1、首先右键桌面空白处打开右键菜单,选择“......
  • Centos7.9+Oracle11g 单机文件系统安装注意事项
    数据库软件(上传至/root目录)p13390677_112040_Linux-x86-64_1of7p13390677_112040_Linux-x86-64_2of7其他软件包(上传至/root目录)compat-libstdc++-33-3.2.3-72.el7.x86_64.rpmpdksh-5.2.14-37.el5_8.1.x86_64.rpm安装软件包#安装compat包rpm-ivhcompat-libstdc++-33-3.2.3-72.el7......
  • 01-React-组件-Ref
    React中获取元素的方式字符串对象回调函数官方文档:https://zh-hans.reactjs.org/docs/refs-and-the-dom.html#gatsby-focus-wrapper第一种传统方式(在React中及其不推荐)importReactfrom"react";classAppextendsReact.PureComponent{constructor(prop......
  • 【2023潇湘夜雨】WIN11_Pro_22H2.23545.1000软件选装纯净版9.23
    【系统简介】=============================================================1.本次更新母盘来自WIN11_Pro_23H2.23545.1000。2.增加部分优化方案,手工精简部分较多。3.OS版本号为23545.1000。精简系统只是为部分用户安装,个别要求高的去MSDN下。4.集成《DrvCeo-2.13.0.8》网卡版、......