C++11 move函数与forward函数解析

1. move和forward函数的效率问题

两个函数本质都是强制转化,都是在编译期完成的,没有任何运行期成本。

2. move函数做了什么事情

move函数无条件地将一个变量转化为其右值引用类型。

3. move的实现

  1. 从理解角度来说,move就是将任意类型转化为其右值引用。
  2. 从函数体实现来说,move就是将参数类型强制转化为返回值类型。

1. 形参如何接收任意类型:万能引用

以下是万能引用接收各种类型参数的情况:

template
void move(T&& t);
  1. 实参是左值引用,解析T为左值引用,t被折叠为左值引用;
  2. 实参是右值引用,解析T为非引用类型,t被折叠为右值引用;
  3. 实参是左值(非引用),解析T为左值引用,t被折叠为左值引用。

2. 返回值如何代表任意类型的右值:remove_reference_t

  1. 引用折叠得到的结果可能是左值和右值,我们需要的只是右值,所以不能使用引用折叠;
  2. 实现方案是,使用remove_reference类型强制构造出右值来:
template
typename remove_reference::type&& move(T&& t);

3. remove_reference的实现原理:模板及特化

//一般版本:适用于普通左值;
template< class T > struct remove_reference      {typedef T type;};
//特化版本:适用于左值引用;
template< class T > struct remove_reference  {typedef T type;};
//特化版本:适用于右值引用;
template< class T > struct remove_reference {typedef T type;};

using remove_reference_t = typename remove_reference::type;

总之,remove_reference/remove_reference_t可以保证得到一个类型的非引用类型。

4. 函数体:一个强制转化(强制将参数转化为返回值类型)

template
typename remove_reference::type&& move(T&& t) {
    return static_cast::type&&>(t);
}

5. 使用c++14简化实现

template
decltype(auto) move(T&& t) {
    using ReturnType = remove_reference_t&&;
    return static_cast(t);
}

4. forward做了什么

forward属于条件强制转化,如果模板参数是左值引用,就啥都不做返回,如果模板参数是非引用类型、右值引用,就进行强制转化为右值引用。

5. forward是如何实现的

1. 参数:左值引用

完美转发场景下,模板函数形参一定是右值引用,而在函数内,形参一定是左值引用类型(因为已经可以取到地址)。所以在完美转发场景下,forward拿到的数据类型一定是左值引用。

template
void forward(remove_reference_t& t);

左值引用的实现,同样使用remove_reference。

2. 模板参数的特点

既然forward拿到的数据类型一定是左值引用,那如何知道其原来的类型到底是左值还是右值呢?所以必须手动告诉forward:

//使用forward,必须有模板参数T
template
void func(T&& t) {
    return func2(std::forward(t));
}

所以可以认为,forward的模板参数和外层调用的模板参数类型是一致的。完美转发场景下,外层模板参数的类型如上面3-1所示的。

3. 返回值:引用折叠的结果

返回值可能是左值也可能是右值,取决于参数,此时符合引用折叠:

template
T&& forward(remove_reference_t& t);

4. 函数体:一个强制转化(强制将参数转化为返回值类型)

template
T&& forward(remove_reference_t& t) {
    return static_cast(t);
}

6. forward理解:完美转发场景下

forward主要用于完美转发场景,从3-1万能引用的解析规则可以看到:

  1. 完美转发实参是左值或者左值引用,模板参数T被解析为左值引用,传递到forward的模板参数是左值引用,啥都不做返回,所以返回值也是左值引用;
  2. 完美转发实参是右值引用,模板参数T被解析为非引用类型,传递到forward的模板参数是非引用类型,forward会将参数转化为右值引用。

结论就是,forward完美还原了完美转发函数的实参类型。

7. move函数与const的不兼容性

考虑以下移动语义:

class Widget {
    Widget(Widget&& w);
};

Widget w;
Widget w1(std::move(w));

1. 移动语义的使用场景

移动语义比复制语义快的场景是:类内部包含资源(比如指针),移动的时候,就是将资源由一个对象移动到另外一个对象。w1掌管资源后,w不应该再可以访问资源,所以最好将w的指针设置为空,即存在修改w的需求。

----》const&&一般没有使用场景。

2. 函数参数的角度来看const

从参数来看,右值引用没有const修饰。而const作为类型的一部分,一旦实参类型存在const类型,那么肯定不会调用到右值引用的重载版本上来。

3. 结论

实参类型包含const,编译器会选择const引用版本的重载,不会选择右值引用,即使其是一个const&&。

8. move与forward的异同点

1. 场景不同

  1. move一般在需要右值引用的地方使用;
  2. forward一般在完美转发的地方使用。

2. 使用方式不同

forward需要显示指定模板参数,如5-2所示。move不需要。

3. forward实现move

根据二者语义,forward的模板参数为非引用类型时,会将形参转化为右值引用返回。所以forward完全可以用来实现move的功能。

string res;
string res1(std::move(res));
string res2(std::forward(res1));

语义上是可以,但是一般不这么使用。

9. move与forward的使用场景

1. move的使用场景

当一个类型实现了移动语义,且这个类型的变量最后一次被使用的时候,才将这个变量使用move返回。因为std::move的使用接下来很可能会改变当前对象。

2. forward的使用场景

完美转发函数内,最后一次使用形参的时候,使用forward修饰。原因和move一样,因为形参可能被转化到右值,然后被修改。

3. 一个应用:return形参

场景描述:当一个函数需要返回的是形参对象,强调不是返回函数内局部变量,无法实行NVO返回值优化。

  1. 为避免按值返回的拷贝代价,按值拷贝时,一般需要使用std::move;
  2. 完美转发中,返回的是引用(返回形参的引用没有问题),可以使用forward修饰返回值。
//move的使用
Widget func(const Widget& aaa) {
    //use aaa;
    ...
    
    return std::move(aaa);
}

//forward的使用
template
T&& func(T&& t) {
    //use t;
    ...
    return forward(t);
}

  1. 注意,此时的使用场景不适合返回函数内局部变量。

你可能感兴趣的:(C++11 move函数与forward函数解析)