std::move()、std::forward<T>、模板类型推断分析
引用折叠原则和完美转发是有联系的,可以说后者是基于前者的某些特性实现的,具体来看一下。
要理解完美转发,需要了解两个知识点:
引用折叠原则(Reference collapsing rules)。
右值函数模版参数类型推导(Template argument deduction)
我们先来分析一下为什么需要使用到move呢?
C++11多出来一个move语义,意图是解决临时对象重复拷贝和释放引发的资源浪费,move与右值引用进行搭配可以完美的解决这个问题。
比如在进行vector中的insert函数的时候,如果模板类型是string,通过move(str1)我们就可以只将string中的指针进行交换,即可实现插入到vector容器中的操作。而不用再进行深拷贝的动作。
需要注意的是,如果使用move进行左值变右值之后,该左值不能再利用,因为它里面的信息可能已经被更改,对应的string就是,指向字符串的指针被打断了。
下面这张图是模板类型推断原则:
这是函数模板参数类型推导中一种比较特殊的情况,这种情况会把模板参数作为右值引用使用,例如:
template<typename T>
void foo(T&&);
其中T为模板类型,T&&为参数类型。这种情况会产生两种结果:
1. 当传给foo函数的参数是一个左值引用时,例如:
int i = 29;
foo(i);//i为左值引用
此时,T的类型为int的左值引用:int&,参数类型为int & &&,(既T&&),结合上面的引用折叠规则,最终参数的类型为int的左值引用:int&。
2. 当传给foo函数的参数是一个右值引用时,例如:
foo(29);
此时,T的类型为int,参数类型为int&&,(既T&&)。
那么,为什么需要forward呢?
我们先来看下以下例子:
template<typename T>
void show1(T && a)
{cout << "我是右值引用" << endl;
}template<typename T>
void show1(T & a)
{cout << "我是左值引用" << endl;
}template<typename T>
void show(T && a)
{show1(forward<T>(a));
}
//我们通过以下进行调用;
show(2);
该函数第进入show之后会调用show1的调用,如果此时没有用forward进行转发的话,会调用到
左值引用版本,而不会调用到右值引用的版本。所有forward就是进行完美转发的作用,当传入的是左值引用
的时候,会调用到左值引用的版本,当调用到右值引用的时候,函数里面也会调用到右值引用的版本 那么他是怎么实现的呢?
通过以上代码的分析,我们可以发现,forward会进行类型的转发。具体实现原理就是上面所说的引用折叠原则。
下面是VS2015下的实现源码。
// TEMPLATE FUNCTION forward
template<class _Ty> inlineconstexpr _Ty&& forward(typename remove_reference<_Ty>::type& _Arg) _NOEXCEPT{ // forward an lvalue as either an lvalue or an rvaluereturn (static_cast<_Ty&&>(_Arg));}template<class _Ty> inlineconstexpr _Ty&& forward(typename remove_reference<_Ty>::type&& _Arg) _NOEXCEPT{ // forward an rvalue as an rvaluestatic_assert(!is_lvalue_reference<_Ty>::value, "bad forward call");return (static_cast<_Ty&&>(_Arg));}
我们就上面那个例子来进行分析:
1、传入的是左值的时候。
void show(int& && a)
{//此时T为 int&类型。a通过引用折叠原则为int&类型show1(forward<int&>(a));
}
此时forward会调用下面这个版本的函数。
constexpr int& && forward(typename remove_reference<int&>::type& _Arg) _NOEXCEPT{ // forward an lvalue as either an lvalue or an rvaluereturn (static_cast<int& &&>(_Arg));}
那么,此时返回的就是int& &&类型,通过引用折叠原则即为 int &,完成了左值的转发
2、传入的是右值的时候。
下面是类型推断调用的时候生成的函数。
void show(int && a)
{//此时T为 int类型。a为 int&& 类型show1(forward<int>(a));
}
相应地就会调用到下面这个forward版本:
constexpr int && forward(typename remove_reference<int>::type&& _Arg) _NOEXCEPT
{ // forward an rvalue as an rvalue
static_assert(!is_lvalue_reference<int>::value, "bad forward call");
return (static_cast<int &&>(_Arg));
}
我们可以看出上面的这个forward会返回右值引用,则实现了右值引用的完美转发。
总结
1、move函数是将一个左值转为右值引用,转换之后,改左值就不能再进行使用了。
2、forward函数是进行类型的转发,可以将左值引用继续转发为左值,右值引用继续转发为右值引用。