前面写了关于左值引用和右值引用,讲了Move Constructor和Move Assignment,这一节讲最后一部分std::move和std::forward,那么这一个小系列就算说完了。
我们现在知道怎么用Move Constructor和Move Assignment,但是我们现在还没有方法将一个Rvalue Reference 绑定到一个Lvalue上。这个时候就需要std::move出场了。std::move的原型:
template
typename remove_reference::type&& move(T&& t){
return static_cast::type&&> (t);
}
这段代码虽然很短,但是并不简单。首先说说T&&,这个是模板参数类型的Rvalue Reference,请将它和单纯的Rvalue Reference区分开,对于模板参数类型的Rvalue Reference有如下公式:
这里面T是具体的类型,T可以是Lvalue或是Rvalue,也可以是Lvalue Reference或是Rvalue Reference。为了说起来更方便,设T的去引用原型是TT。
当T == TT时,T deduced TT&
当T == TT&时,T&& == TT& && == TT&
当T == TT&&时,T&& = TT&& && == TT&&
这里把T&也一并说出来,这个是模板参数类型的Lvalue Reference。
当T == TT时,T deduced TT&
当T == TT&时,T& == TT& & == TT&
当T == TT&&时,T& == TT&& & == TT&
有了上面的公式,理解起来就简单多了。对于输入T&&,依据上面公式可以接纳任何类型;而对于输出typename remove_reference
string s1("Hello"), s2;
s2 = std::move(string("world")); //moving from a Rvalue,还原成string&& std::move(string &&t)
s2 = std::move(s1); //moving from a Lvalue, 还原成string&& std::move(string &t)
这里面还要说说static_cast,static_cast还是很强大的,他不光有那些常规的功能,他也能够显式改变一个引用的类型。对于Lvalue Reference转换成Rvalue Reference,我们也可以使用static_cast去做转换,但是使用std::move显然更加方便。
先看一个例子:
template
void flip1(F f, T1 t1, T2 t2){
f(t2, t1);
}
可以看到这个模板函数,定义了一个函数形式(接受两个参数),和两个参数。这个函数看起来没有什么大问题,但是实际上有大问题,再接着看:
void f (int v1, int &v2){
cout << v1 << " " << ++v2 << endl;
}
f(42, i); //f是能够改变i的值的
flip1(f, j, 42) //通过flip1再调用f,这个时候f不再能够改变j的值
f不再能够改变j的值,这就是很大的问题,不符合最初的设计初衷。我们把模板还原:
void flip1(void(*f)(int, int&), int t1, int t2);
j的值通过值传递给t1,实际上是一个copy操作,而f中的引用参数绑定到t1, 而不是j
所以,对于flip1函数,我们需要达到一个目标,我们要能够保证在f中能够改变参数的值,甚至对这一类模板函数,我们还要能够保证参数的是否是const。这里要使用上面提到的“模板参数类型的Rvalue Reference”。如果我们使用T1&&和T2&&,我们能够保留下flip1中参数的属性(包括const)。
template
void flip2 (F f, T1 &&t1, T2 &&t2){
f(t2, t1);
}
通过前面的Rvalue Reference公式, 能够得到
void flip1(void(*f)(int, int&), int& t1, int& t2); //int deduced to int&
这样就能够在f中改变j的值了。
下面再看一个函数
void g(int &&i, int& j){
cout << i << j << endl;
}
flip2(g, i, 42); //不能够将一个LValue Reference用于初始化一个RValue Reference
这里面42是一个RValue,但是当t2绑定到它的时候,t2本身是一个LValue Reference。这时候t2再传入给g就会有问题,因为不能够将一个LValue Reference用于初始化一个RValue Reference 。
基于以上两个例子,会发现将输入参数恢复到他们本来的属性很重要,否则会出现问题。这个时候就用到了std::forwad函数。先看看std::forward的原型:
template
T&& forward(typename std::remove_reference::type& param) //左值引用版本
{
return static_cast(param);
}
template
T&& forward(typename std::remove_reference::type&& param) //右值引用版本
{
//param被右值初始化时,T应为右值引用类型,如果T被绑定为左值引用则报错。
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
" substituting _Tp is an lvalue reference type");
return static_cast(param);
}
这里面无论是std::remove_reference
所以,基于以上,flip函数应该写为:
template
void flip(F f, T1 &&t1, T2 &&t2){
f(std::forward(t2), std::forward(t1));
}
以上就分析完std::move和std::forward了。