move vs forward
c++ primer第五版,书真的好,所有的东西在里面都能找到
通用的规则:const是 底层的,不是顶层的。就是说 只要函数参数或者 类型有const最终推导出的就有const
从左值引用函数传参推断类型
对于
template<typename T>
void f1(T&)
这要求实参必须是一个左值
f1(i); //模板类型是i
f1(ci); //ci
template<typename T>
void f1(const T&)
可以传递任何类型,左值、右值、临时对象。推导出的类型不会带有const,const 已经是函数参数类型的一部分
template<typename T>
void f1(T&& )
可以传递右值,T推导出的类型就是右值实参的类型
当我们将一个左值传递给函数的右值引用参数,且此右值引用指向模板参数时,编译器推导模板类型参数为实参的左值引用类型
也就是
template<typename T>
void f1(T&& )
int i;
f1(i);
参数的T 被推到为 int&
X& &、X& && 、X&& &都将折叠为类型X&
X&& &&,推导为X&&
? 这个词是大家传出来的
? 啥意思呢就是:
template<typename T>
void f1(T&& );
? 这种模板 右值引用形式,可以传递任何参数给f1
1. 当函数参数是左值,那么推导出的T类型是 T&, 引用折叠后就是 T&
2. 当函数参数是右值,那么推导出的T类型就是T,函数参数是 T&&
但是这样对于编码就带来了不便,你不清楚 参数是左值还是右值,无法准确针对这个来写内部逻辑代码(左值涉及到拷贝构造、右值设计到移动构造)
适用场景:
? 模板转发
? 模板重载
? 使用std::move 获得绑定到左值的右值引用,模板的转换推导都是在编译期间完成的
? 看一下move的源码
template<typename _Tp>
constexpr typename std::remove_reference<_Tp>::type&&
move(_Tp&& __t) noexcept
{ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
template<typename _Tp>
struct remove_reference
{ typedef _Tp type; };
template<typename _Tp>
struct remove_reference<_Tp&>
{ typedef _Tp type; };
template<typename _Tp>
struct remove_reference<_Tp&&>
{ typedef _Tp type; };
我们来两个例子看下:
string str1 ="12";
string str2 = std::move(string("12"));
string str3 = std::move(str1);
第一个是临时对象的转移
第二个是左值转换为右值并转移
对于第一个
_Tp的类型就是 string
然后返回值转换为了 string&&
对于第二个
实参为左值,那么_Tp的类型就是 string&
经过remove_reference解引用,还是 string&&
? 当我们在函数内部需要把参数原封不动来调用其他函数,我们需要机制确保这种要求,基本就是 和 模板类型&&配合,只有这种才不确定
? 默认的只要是一个变量,即使是右值引用类型,那么他也是左值变量,也只有匹配到左值的函数而不是右值,这就和我们期待的不同了
例子1
void f(int&& value) {
std::cout << "void f(int&& value)" << std::endl;
}
void f(int& value) {
std::cout << "void f(int& value)" << std::endl;
}
int main()
{
int&& test = 1;
f(test);
int test1 = 1;
f(test1);
f(1);
return 0;
}
输出:
void f(int& value)
void f(int& value)
void f(int&& value)
看出传递 接收类型是右值的变量,实际匹配的是左值
void f(int v1, int& v2) {
std::cout << v1 << v2++ << std::endl;
}
template<typename F, typename T1, typename T2>
void flip1(F f, T1 t1, T2 t2) {
f(t1, t2);
}
int main()
{
int v1 = 1;
int v2 = 2;
f(v1, v2);
flip1(f, v1, v2);
return 0;
}
f的调用v2改变
但是 flip1的调用 v2实际未发生改变,因为 f(int v1, int& v2)绑定到 T1 t1, T2 t2而不是实际的
解决办法:通过将函数参数定义为一个指向模板类型参数的右值引用,我们可以保持对应实参的所有类型信息。使用引用参数可以保持const
? 也就是
template<typename F, typename T1, typename T2>
void flip1(F f, T1&& t1, T2&& t2) {
f(t1, t2);
}
传递左值给 flip1,推导为T1&,然后往下继续传递
那么右值呢?
void f(int &v1, int&& v2) {
std::cout << v1 << v2++ << std::endl;
}
template<typename F, typename T1, typename T2>
void flip1(F f, T1&& t1, T2&& t2) {
f(t1, t2);
}
int main()
{
int v1 = 1;
int v2 = 2;
f(v1, v2);
flip1(f, v1, 2);
return 0;
}
就出现了,传递右值内部变成左值的问题
最终解决办法:std::forward
std::forward必须使用显示模板实参调用,返回值是T&&
就可以把上面的修改为
template<typename F, typename T1, typename T2>
void flip1(F f, T1&& t1, T2&& t2) {
f(std::forward<T1>(t1), std::forward<T2>(t2));
}
看一下源码
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }
/**
* @brief Forward an rvalue.
* @return The parameter cast to the specified type.
*
* This function is used to implement "perfect forwarding".
*/
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
" substituting _Tp is an lvalue reference type");
return static_cast<_Tp&&>(__t);
}
remove_reference也是解引用的作用,
那么 如果传递的是左值,推导的_Tp 为 int&, std::remove_reference<_Tp>::type&& 参数就是 int&&. std::remove_reference<_Tp>::type& 参数就是 int&,匹配到第一个,然后转换为 int& &&,最终是int&
那么如果传递的是右值,推导的_Tp 为 int,td::remove_reference<_Tp>::type&& 参数就是 int&&. std::remove_reference<_Tp>::type& 参数就是 int&,匹配到第一个,然后转换为 int &&,最终是int&&
如果是类型右值也一样,还是右值
利用了解引用 + 模板匹配
template<typename T>
void swap1(T& left, T& right) {
T temp = std::move(right);
right = std::move(left);
left = temp;
}
利用移动的概念,轻松写出
? 我们在源码里面常常看见
template<typename _Alloc, typename... _Args>
shared_ptr(_Sp_make_shared_tag __tag, const _Alloc& __a,
_Args&&... __args)
: __shared_ptr<_Tp>(__tag, __a, std::forward<_Args>(__args)...)
{ }
这种使用就是准确的 把对应类型完美转发出去
原文:https://www.cnblogs.com/zero-waring/p/14809610.html