std::reference_wrapper是C++11引入的新特性,定义在 <functional>头文件中
template< class T > class reference_wrapper;reference_wrapper 将一个引用包装成一个可拷贝的,可分配的对象,是引用的包装器,她通常作为一种将引用存储在标准容器(比如std::vector)中的的机制,因为标准容器通常是无法存储引用的。
比如:
容器里是 std::reference_wrapper 对象 std::vector<std::reference_wrapper<ParticipantObserver>> observers_;reference_wrapper是一个可拷贝构造和可赋值构造的包装器,它可以将一个引用包装成对象或者将一个引用包装成模板参数类型T的函数; std::reference_wrapper的实例对象可以保存和存储在标准容器当中,但是会隐式的转换为T&,因此std::reference_wrapper可以作为将把被其包裹类型为参数的函数的实参。
如下面的代码中,函数func的参数类型为int,而传递给func的参数确是std::reference_wrapper类型的对象。这个特性是保证reference_wrapper对象可以作为函数实参的关键。
void func(int param){ std::cout << param << std::endl; } int a = 3; std::reference_wrapper<int> ra = a; func(ra);若reference_wrapper包裹的引用是可以调用的,则reference_wrapper对象也是可调用的; std::ref 和std::cref 通常用来产生一个reference_wrapper对象; reference_wrapper 常通过引用传递对象给std::bind函数或者std::thread构造函数。std::reference_wrapper可能的实现
namespace detail { template <class T> constexpr T& FUN(T& t) noexcept { return t; } template <class T> void FUN(T&&) = delete; } template <class T> class reference_wrapper { public: // types typedef T type; // construct/copy/destroy template <class U, class = decltype( detail::FUN<T>(std::declval<U>()), std::enable_if_t<!std::is_same_v<reference_wrapper, std::remove_cvref_t<U>>>() )> constexpr reference_wrapper(U&& u) noexcept(noexcept(detail::FUN<T>(std::forward<U>(u)))) : _ptr(std::addressof(detail::FUN<T>(std::forward<U>(u)))) {} reference_wrapper(const reference_wrapper&) noexcept = default; // assignment reference_wrapper& operator=(const reference_wrapper& x) noexcept = default; // access constexpr operator T& () const noexcept { return *_ptr; } constexpr T& get() const noexcept { return *_ptr; } template< class... ArgTypes > constexpr std::invoke_result_t<T&, ArgTypes...> operator() ( ArgTypes&&... args ) const { return std::invoke(get(), std::forward<ArgTypes>(args)...); } private: T* _ptr; }; // deduction guides template<class T> reference_wrapper(T&) -> reference_wrapper<T>; 总结了下,可能以下三种情况需要用到:vector里不能直接存储引用,又不想做拷贝丢入lamba或者模板函数的参数是引用,如果设计意图就是执行完毕后被对象会被修改也可以使用如下代码定义reference_wrapper对象:
reference_wrapper r=x;// or auto r = ref(x);通过r对象的get函数(r.get()),可以获取到包装的元素。
reference_wrapper和move语义是紧密相连的,其可以节省右值对象的复制构造开销。
std::reference_wrapper使用实例
#include <algorithm> #include <list> #include <vector> #include <iostream> #include <numeric> #include <random> #include <functional> int main() { std::list<int> l(10); std::iota(l.begin(), l.end(), -4); std::vector<std::reference_wrapper<int>> v(l.begin(), l.end()); // can't use shuffle on a list (requires random access), but can use it on a vector std::shuffle(v.begin(), v.end(), std::mt19937{std::random_device{}()}); std::cout << "Contents of the list: "; for (int n : l){ std::cout << n << ' '; } std::cout << "\nContents of the list, as seen through a shuffled vector: "; for (int i : v){ std::cout << i << ' '; } std::cout << "\n\nDoubling the values in the initial list...\n\n"; for (int& i : l) { i *= 2; } std::cout << "Contents of the list, as seen through a shuffled vector: "; for (int i : v){ std::cout << i << ' '; } }Possible output:
Contents of the list: -4 -3 -2 -1 0 1 2 3 4 5 Contents of the list, as seen through a shuffled vector: 3 -3 -2 2 0 4 5 -4 -1 1 Doubling the values in the initial list... Contents of the list, as seen through a shuffled vector: 6 -6 -4 4 0 8 10 -8 -2 2引用数组的创建
还可以用来创建引用数组,例如:
int x=5,y=7,z=8; std::reference_wrapper<int> arr[]{x,y,z};std::reference_wrapper在泛型代码中用处广泛,它存储的是对象的指针,有引用的全部功能,还实现了引用的拷贝(包括拷贝构造和拷贝赋值),可以在程序中存储引用而不是整个对象。 reference_wrapper和shared_ptr如何选择?两者都可以实现指针层面的复制和操作,但是前者不允许默认构造函数,在容器中也不能使用resize等方法。另外可能还有一些不同之处,但是基本上没有太大区别了。
std::ref()是个模板函数,其函数原型为:
//reference (1) template <class T> reference_wrapper<T> ref (T& elem) noexcept; //copy (2) template <class T> reference_wrapper<T> ref (reference_wrapper<T>& x) noexcept; //move (3) 明确禁止prvalue和x值值类别类型的对象从与所述功能被使用,并且const T&&不结合到所有prvalue和x值的对象。 template <class T> void ref (const T&&) = delete;std::ref()用来构建一个reference_wrapper
构建一个reference_wrapper类型对象,该对象拥有传入的elem变量的引用。如果参数本身是一个reference_wrapper类型x,则创建一个x的副本。
参数:
elem:An lvalue reference, whose reference is stored in the object.
x:A reference_wrapper object, which is copied.
返回值:
一个拥有一个T类型元素的reference_wrapper对象
示例:
// ref example #include <iostream> // std::cout #include <functional> // std::ref int main () { int foo (10); auto bar = std::ref(foo); ++bar; std::cout << foo << '\n'; return 0; }Output:
11std::cref()
构造一个reference_wrapper常数类型。
std::ref主要是考虑函数式编程(如std::bind)在使用时,是对参数直接拷贝,无法传入引用,故引入std::ref()。使用std::ref可以在模板传参的时候传入引用。
ref能用包装类型reference_wrapper来代替原本会被识别的值类型,而reference_wrapper能隐式转换为被引用的值的引用类型。
不仅仅是在使用bind时,在使用thread进行编程时,也会发生这样的问题,thread的方法传递引用的时候,必须外层用ref来进行引用传递,否则就是浅拷贝。
std::bind()函数需要传入引用
#include <functional> #include <iostream> //std::ref主要是考虑函数式编程(如std::bind)在使用时,是对参数直接拷贝,而不是引用 void f(int &a,int &b,int &c) { std::cout<<"in function a = "<<a<<" b = "<<b<<" c = "<<c<<std::endl; a += 1; b += 10; c += 100; } int main(){ int n1 = 1 ,n2 = 10,n3 = 100; function<void()> f1 = bind(f,n1,n2,ref(n3)); f1(); std::cout<<"out function a = "<<n1<<" b = "<<n2<<" c = "<<n3<<std::endl; f1(); std::cout<<"out function a = "<<n1<<" b = "<<n2<<" c = "<<n3<<std::endl; return 0; }输出:
in function a = 1 b = 10 c = 100 out function a = 1 b = 10 c = 200 in function a = 2 b = 20 c = 200 out function a = 1 b = 10 c = 300在这里我们可以发现,在用bind的时候,如果不用ref时,调用函数是没有引用的。
std::thread()需要传入引用
查看thread的源代码,其构造函数依赖于一个rvalue-reference类型的variaic templates参数列表:
template::type, thread>::value>::type>explicit thread(_Fn&& _Fx, _Args&&... _Ax){ // construct with _Fx(_Ax...) _Launch(&_Thr, _STD make_unique, decay_t<_Args>...> >( _STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...)); }线程函数的参数按值移动或复制。如果引用参数需要传递给线程函数,它必须被包装(例如使用std :: ref或std :: cref)。
#include <functional> #include <iostream> #include <thread> void method(int & a){ a += 5;} using namespace std; int main(){ int a = 0; // each reference used by the threads would refer to the same object. thread th(method,ref(a)); th.join(); cout << a <<endl; /*thread th2(method, a); //浅拷贝 th2.join(); cout << a <<endl;*/ return 0; }在std::promise范例中,使用了std::ref将future对象传递给引用参数类型的任务函数。
std::promise示例
std::promis<int> pr; std::thread t([](std::promise<int>& p) {p.set_value_at_thread_exit(9);},std::ref(pr)); std::future<int> f = pr.get_future(); auto r = f.get();如果直接传入pr,将会出现编译错误:
error C2661: “std::tuple,std::promise>::tuple”: 没有重载函数接受 2 个参数说明函数调用的参数类型不匹配。
std::ref()和引用的区别
std::ref只是尝试模拟引用传递,并不能真正变成引用,在非模板情况下,std::ref根本没法实现引用传递,只有模板自动推导类型时,ref能用包装类型reference_wrapper来代替原本会被识别的值类型,而reference_wrapper能隐式转换为被引用的值的引用类型。
参考:
https://blog.csdn.net/commshare/article/details/107133634
