引言

之前因为调整样式把博客园的样式毁了,所以一直在自己的另一个博客上更新,有兴趣的可以去观望一下:http://blog.yunlambert.top/最近还是把博客园拾起来吧。。。。。

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

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),该函数的参数为一个vector引用、一个计算结果acm的引用和记录vector首末位置的index。其中的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);
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>
#include <functional>
using namespace std;
int main()
{
int x = 5;
std::cout << boolalpha << is_same<int&, decltype(ref(x))>::value;
return 0;
}

输出答案果然是false!那么如果std::ref()返回的不是对象的引用,返回的是什么?查一下手册可以发现:函数模板 refcref 是生成 std::reference_wrapper 类型对象的帮助函数,它们用模板实参推导确定结果的模板实参。所以std::ref()返回的实际上是一个reference_wrapper而不是T&,可以从一个指向不能拷贝的类型的对象的引用生成一个可拷贝的对象。 std::reference_wrapper 的实例是对象(它们可被复制或存储于容器),但它们能隐式转换成 T& ,故能以之为以引用接收底层类型的函数的参数。

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

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

变为true了。reference_wrapper&并不一样,但是利用get()函数就是&类型。但是为什么在多线程那个例子中要使用std::ref()呢?

原因是,考虑了函数式编程(如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
} 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';
}

得到的答案为:

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的设计者认为bind默认应该采用拷贝,如果使用者有需求,加上std::ref()即可。同理std::thread也是这样。

结论

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

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

std::ref() 与 &的更多相关文章

  1. 为什么C++11引入了std::ref?

    C++本身有引用(&),为什么C++11又引入了std::ref? 主要是考虑函数式编程(如std::bind)在使用时,是对参数直接拷贝,而不是引用.如下例子: #include <f ...

  2. C++11 std::ref使用场景

    C++本身有引用(&),为什么C++11又引入了std::ref(或者std::cref)? 主要是考虑函数式编程(如std::bind)在使用时,是对参数直接拷贝,而不是引用.如下例子: # ...

  3. c++11之为什么C++11引入了std::ref?

    C++本身有引用(&),为什么C++11又引入了std::ref? 主要是考虑函数式编程(如std::bind)在使用时,是对参数直接拷贝,而不是引用.如下例子: #include <f ...

  4. c++11多线程---std::ref和std::cref

    std::ref和std::cref   解释 std::ref 用于包装按引用传递的值. std::cref 用于包装按const引用传递的值.   为什么需要std::ref和std::cref ...

  5. std::ref和std::cref使用(转载)

    转载于:https://blog.csdn.net/lmb1612977696/article/details/81543802 std::ref和std::cref 解释: std::ref 用于包 ...

  6. std::bind与std::ref, why and how

    首先解释下为什么有时候需要bind. 我们可以用bind从函数T add(T a, T b)造出个inc()来,即把b写死为1.这个例子本身比较傻,但有不傻的应用. template<typen ...

  7. boost和std中的thread的引用参数

    boost 1.60.0 先上代码: #include <boost/thread.hpp> #include <iostream> void add(int &i) ...

  8. 用C++11的std::async代替线程的创建

    c++11中增加了线程,使得我们可以非常方便的创建线程,它的基本用法是这样的: void f(int n); std::thread t(f, n + 1); t.join(); 但是线程毕竟是属于比 ...

  9. std::thread

    std::shared_ptr<std::thread> m_spThread; m_spThread.reset(new std::thread(std::bind(&GameS ...

随机推荐

  1. bzoj 2118: 墨墨的等式 spfa

    题目: 墨墨突然对等式很感兴趣,他正在研究\(a_1x_1+a_2y_2+ ... +a_nx_n=B\)存在非负整数解的条件,他要求你编写一个程序,给定\(N,\{a_n\}\)以及\(B\)的取值 ...

  2. 使用MDI窗体实现多窗口效果

    本文章已收录于:   C#MDI窗体实现多窗口效果   Visual C#是微软公司推出的下一代主流程序开发语言,他也是一种功能十分强大的程 序设计语言,正在受到越来越多的编程人员的喜欢.在Visua ...

  3. MYSQL主从复制配置遇到的问题

    在进行配置从服务器时遇到的错误. mysql> change master to master_host='192.168.136.129',master_user='repl',master_ ...

  4. 关于Snoop的用法

    snoop是开发wpf应用程序的利器.用它可以观察WPF的可视树,监听事件,更改元素属性等. 下面我介绍下snoop一些用法. 1.获取指定应用程序的UI   打开snoop,选择"Drag ...

  5. Poj 2328 Guessing Game(猜数字游戏)

    一.题目大意 两个小盆友玩猜数字游戏,一个小盆友心里想着1~10中的一个数字,另一个小盆友猜.如果猜的数字比实际的大,则告诉他"too high",小则"too low& ...

  6. tx2在自制载板上无法识别usb以及pcie无法读取数据

    注意使用的系统版本是Jetpack-3.1,其它版本的系统上没有测试过!!! 刷机时替换dtb文件: 将Jetpack刷机包 64_TX2/Linux_for_Tegra_tx2/kernel/dtb ...

  7. Spring boot 学习二:入门

    1: 需要的环境: JDK:至少JDK7才支持Spring boot maven:至少3.2 spring-boot:1.2.5.RELEASE(在pom.xml中指定) 2: 创建一个maven工程 ...

  8. springMVC绑定json参数之一

    一.SpringMVC @RequestBody接收Json对象字符串 以前,一直以为在SpringMVC环境中,@RequestBody接收的是一个Json对象,一直在调试代码都没有成功,后来发现, ...

  9. linux命令-tar打包和压缩并用

    tar在打包的时候进行压缩 支持 gzip bzip2 xz 格式 -z  gzip格式 -j  bzip2格式 -J  xz格式 压缩打包 [root@wangshaojun ~]# tar -zc ...

  10. SpringMVC 学习笔记(请求方法的返回值和参数)

    在用注解对配置 处理器时,一般是一个方法处理一个请求,不同方法的返回类型有着不同的意义. 返回值为 ModelAndView 类型 ModelAndView 是Model 和 View 的一个集合类型 ...