C++11中std::reference_wrapper()和std::ref()

1、std::reference_wrapper

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等方法。另外可能还有一些不同之处,但是基本上没有太大区别了。

2、std::ref

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:

11

std::cref()

构造一个reference_wrapper常数类型。

3、c++11 为什么引入std::ref(),std::ref()和引用的区别

       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

猜你喜欢

转载自blog.csdn.net/sunlin972913894/article/details/107130322