深入理解std::move和std::forward

    前面写了关于左值引用和右值引用,讲了Move Constructor和Move Assignment,这一节讲最后一部分std::move和std::forward,那么这一个小系列就算说完了。

    std::move

    我们现在知道怎么用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::type&& == TT&&,所以返回原类型的Rvalue 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显然更加方便。

    std::forward

     先看一个例子:

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::type&还是std::remove_reference::type&&,他们和T&&是有区别的,他们是真实类型的LValue Reference和RValue Reference,所以会根据参数真实的类型匹配到对应的forward函数中。而static_cast,这里面的T&&又能够完全还原原有的参数类型。

        所以,基于以上,flip函数应该写为:

template 
void flip(F f, T1 &&t1, T2 &&t2){
    f(std::forward(t2), std::forward(t1));
}

         以上就分析完std::move和std::forward了。

你可能感兴趣的:(C,std)