std::ref() 与 &

引言

之前因为调整样式把博客园的样式毁了,所以一直在自己的另一个博客上更新,有兴趣的可以去观望一下: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来进行引用传递。

原文地址:https://www.cnblogs.com/yunlambert/p/10952189.html

时间: 2024-07-28 18:44:13

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

c++11 std::ref使用场景

C++本身有引用(&),为什么C++11又引入了std::ref? 主要是考虑函数式编程(如std::bind)在使用时,是对参数直接拷贝,而不是引用.如下例子: #include <functional> #include <iostream> void f(int& n1, int& n2, const int& n3) { std::cout << "In function: " << n1 <

c++11 std::ref std::cref

参考: C++已经有了引用操作符&为什么C++11还要引入std:ref std::ref和std::cref使用 &是类型说明符,而std::ref是一个函数,返回std::reference_wrapper(类似于指针) 为什么需要std::ref?(std::cref类似) 主要是考虑到c++11中的函数式编程,例如:std::bind std::bind在使用时,是对参数直接拷贝,而不是引用 发现这个问题的契机是在使用thread的标准库时 #include<iostream

STL C++ std::bind操作例子,仿函数操作配合算法库操作

1.stl::bind 和std::mem_fun_ref系列的配合使用出现了问题,多参形式不知道如何组织.适配器的操作真心难受!!!只能迷迷糊糊地用着.要使用非质变算法时需要作用于容器时只能考虑lambda或者transfer操作.待续 // functor-adapter_p431.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <algorithm>//元素操作算法 #include <functiona

C++——多线程编程(一)std::thread

(一)与C++11多线程相关的头文件 C++11 新标准中引入了四个头文件来支持多线程编程,他们分别是< atomic> ,< thread>,< mutex>,< condition_variable>和< future>. ?< atomic>:该头文主要声明了两个类, std::atomic 和 std::atomic_flag,另外还声明了一套 C 风格的原子类型和与 C 兼容的原子操作的函数. ?< thread>

c++11特性之std::thread--进阶二

继续C++11的std::thread之旅! 下面讨论如何给线程传递参数 这个例子是传递一个string #include <iostream> #include <thread> #include <string> void thread_function(std::string s) { std::cout << "thread function "; std::cout << "message is = &qu

C++11 并发指南------std::thread 详解

参考: https://github.com/forhappy/Cplusplus-Concurrency-In-Practice/blob/master/zh/chapter3-Thread/Introduction-to-Thread.md#stdthread-%E8%AF%A6%E8%A7%A3 本节将详细介绍 std::thread 的用法. std::thread 在 <thread> 头文件中声明,因此使用 std::thread 需包含 <thread> 头文件. &

C++11多线程——&lt;future&gt;之std::promise学习

一  <future>头文件简介 1 Classes std::future std::future_error std::packaged_task std::promise std::shared_future 2 Functions std::async std::future_category 二 std::promise类 1 std::promise class statement Template <class T> promise; Template <cla

浅谈std::bind的实现

bind这个东西争议很多,用起来很迷,而且不利于编译优化,很多人都推荐用lambda而非bind.简单说,bind就是通过库抽象实现了lambda里需要写进语言标准的东西,变量捕获,参数绑定,延迟求值等.但是以此带来的缺陷就是,虽然bind生成的可调用对象的结构是编译期确定的,但是它的值,尤其是被调用的函数,全部是在运行期指定的,并且可调用对象也只是一个普通的类,因此很难进行优化.除此之外,标准库的bind实现,只提供了20个placeholder进行参数绑定,无法扩展,这也是实现的一个坑.因此

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

c++11中增加了线程,使得我们可以非常方便的创建线程,它的基本用法是这样的: void f(int n); std::thread t(f, n + 1); t.join(); 但是线程毕竟是属于比较低层次的东西,有时候使用有些不便,比如我希望获取线程函数的返回结果的时候,我就不能直接通过thread.join()得到结果,这时就必须定义一个变量,在线程函数中去给这个变量赋值,然后join,最后得到结果,这个过程是比较繁琐的.c++11还提供了异步接口std::async,通过这个异步接口可以