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
构造一个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