一、通用引用
通用引用,允许其绑定右值(就像右值引用那样)和左值(就像左值引用那样)。而且,它们可以绑定 const 或者非 const 对象,可以绑定 volatile 和非 volatile 对象,还可以绑定 const 和 volatile 同时作用的对象。它们实际上可以绑定任何东西。构成通用引用有两个条件:
-
必须精确满足 T&& 这种形式(即使加上 const 也不行)
-
类型T必须是通过推断得到的(最常见的就是模板函数参数)
二、引用折叠
引用折叠规则:
- X& &,X& &&,X&& & 折叠为:X&
- X&& && 折叠为:X&&
三、std::move 的工作原理
由以上知识,就可以解释一下 std::move 的工作原理了:
template <typename T>
typename remove_reference<T>::type &&move(T &&t)
{
return static_cast<typename remove_reference<T>::type &&>(t);
}
首先,move 函数的参数类型是通用引用 T&&,可以绑定任意类型参数,其次,返回值是 remove_reference<T> 的 type 成员类型的右值引用,比如 T 被推导为 int 或者 int&,则remove_reference<int&>::type 为 int 类型,返回值类型为 int&&,最后,函数体中 static_cast 内的转换过程类似,虽然不能隐式的将一个左值转换为一个右值引用,但是通过 static_cast 显式转换时允许的(把左值截断问题缩小在使用std::move代码的范围内)。
四、完美转发
有时候,某些函数需要将其实参连同类型(const、左值、右值等属性)不变的转发给其他函数。
template <typename F, typename T>
void sender(F receiver, T t) //sender函数,接受一个可调用对象和一个模板参数类型的参数
{
receiver(t); //sender需要将自己的参数t转发给receiver函数
}
一般情况下这个函数能工作,但是当它调用一个接受引用类型参数的函数时就会有问题:
void rec(int &i) { ++i; }
int j = 1;
rec(j);
cout << j << endl; //输出 j 为 2;
//但是通过 sender 调用时:
template <typename F, typename T>
void sender(F receiver, T t)
{
receiver(t);
}
sender(rec, j);
cout << j << endl; //输出 j 为 1 。
其原因是 j 传递给 sender 函数,推断出 T 为 int 类型而非引用,j 的值是被拷贝到形参 t 中的,因此对 t 值的改变不会反应到 j 中。
这时联想到我们讲的通用引用,将模板参数类型定义为 T&&,接受左值时,T 会被推断为左值引用类型,经过一次引用折叠,得参数 t 的类型为左值引用,它对应实参的 const 属性和左值、右值属性都将得到保持:
template <typename F, typename T>
void sender(F receiver, T &&t) //通用引用
{
receiver(t);
}
void rec(int &i)
{
++i;
}
sender(rec, j);
cout << j << endl; //OK!输出 j 为 2 。
但是这儿又会遇到另一个问题:
template <typename F, typename T>
void sender(F receiver, T &&t)
{
receiver(t);
}
void rec(int &&i) //现在rec接受一个右值引用参数
{
++i;
}
int j = 1;
sender(rec, j); //错误:无法从一个左值实例化int&&
sender(rec, 1); //错误:无法从一个左值实例化int&&
当我们试图对一个接受右值引用的函数转发参数时,会报以上错误,不论我们传递给 sender 函数的是一个左值还是右值。原因是传递给 rec 函数中形参 i 的是 sender 函数中的参数 t,函数参数和其他变量一样都是左值表达式!所以会出现将左值绑定到右值引用的错误。
这时需要用到 forward 函数来保证:当 sender 函数接受一个右值实参,转发给 rec 函数时仍然能保持其右值属性。forward 函数定义在 <utility> 头文件中。
//forward函数
//lvalue (1)
template <typename Type>
Type &&forward(typename remove_reference<Type>::type &arg) noexcept
{
return (static_cast<Type &&>(arg));
}
//rvalue (2)
template <typename Type>
Type &&forward(typename remove_reference<Type>::type &&arg) noexcept
{
return (static_cast<Type &&>(arg));
}
forward 函数的工作原理:由 arg 接受的实参类型推断出 Type 类型。
forward 函数必须通过显式模板实参来调用,它跟通用引用配合可以保存原始实参的所有特性,回到我们的例子:
template <typename F, typename T>
void sender(F receiver, T &&t)
{
receiver(std::forward<T>(t));
}
当传递给 sender 的是一个右值——比如 10 时,推断出来的 T 是一个普通类型即 int,传给 forward 的形参 arg 的实参就是一个 int,从而推出 Type 是 int,这时调用 std::forward<int>(t) 返回的是 int&&,保存了原实参的右值属性。
当传递给 sender 的是一个左值——比如 j 时,这时我们推断出来的 T 应该是一个 int& 了,调用 std::forward<int&>(t) 返回 int & &&(无效代码,演示)折叠成为 int&,保存了原实参的左值属性。
转载于:https://blog.youkuaiyun.com/qq_38216239/article/details/80815142
(SAW:Game Over!)