C++程序员应了解的那些事(79)完美转发 std::forward()

目录

【移动语义std::move】

【完美转发 std::forward()的引入①】

【拓展:完美转发的引入②与引用折叠】

※一旦定义中出现了左值引用,引用折叠总是优先将其折叠为左值引用!!


【移动语义std::move】

       编译器只对右值引用才能调用转移构造函数和转移赋值函数,而所有命名对象都只能是左值引用,如果已知一个命名对象不再被使用而想对它调用转移构造函数和转移赋值函数,也就是把一个左值引用当做右值引用来使用,怎么做呢?标准库提供了函数 std::move,这个函数以非常简单的方式将左值引用转换为右值引用。

int a;
int &&r1 = a;            // 编译失败
int &&r2 = std::move(a); // 编译通过

  其实对于std::move来说,它做的事情大致如下(当然真正的实现并非如此):

template<typename T>
T&& move(T& val)
{
    return static_cast<T&&>(val);
}

 move 只是纯粹的将一个左值转化为了一个右值,STL实现基本都已经实现了移动语义,相当于对于 vector<T>::push_back()有两个版本的实现,大致如下:

template<typename T>
class Vector
{
    void push_back(T& lval);
    void push_back(T&& rval);
};

 而对应的类型 T 也实现了移动拷贝,如下:

class T
{
    T(T& other)
    {
        // copy constructor
    }
    T(T&& other)
    {
        // move constructor
    }
};

【完美转发 std::forward()的引入①】

       当我们将一个右值引用传入函数时,它在实参中就有了命名,所以继续往下传或者调用其他函数时,根据C++ 标准的定义,这个参数变成了一个左值!~那么它永远不会调用接下来函数的右值版本,这可能在一些情况下造成拷贝。为了解决这个问题C++ 11引入了完美转发,根据右值判断 推导出调用forward传出的值,若原来是一个右值,那么他转出来就是一个右值,否则为一个左值。这样的处理就完美的转发了原有参数的左右值属性,不会造成一些不必要的拷贝。代码如下:

#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
    string A("abc");
    string&& Rval = std::move(A);
    string B(Rval);      // this is a copy , not move.
    cout << A << endl;   // output "abc"
    string C(std::forward<string>(Rval));  // move.
    cout << A << endl;   /* output "" */

    return 0;
}

std::forward

       右值引用类型是独立于值的,一个右值引用参数作为函数的形参,在函数内部再转发该参数的时候它已经变成一个左值,并不是他原来的类型。如果我们需要一种方法能够按照参数原来的类型转发到另一个函数,这种转发类型称为完美转发。

<示例-1>
template<typename T>
void print(T& t){
    cout << "lvalue" << endl;
}
template<typename T>
void print(T&& t){
    cout << "rvalue" << endl;
}
template<typename T>
void TestForward(T && v){
    print(v);
    print(std::forward<T>(v));
    print(std::move(v));
}
int main(){
    TestForward(1);
    int x = 1;
    TestForward(x);
    TestForward(std::forward<int>(x));// 完美传递1的类型!!
    return 0;
}
输出:
lvalue
rvalue
rvalue

lvalue
lvalue
rvalue

lvalue
rvalue
rvalue

注:右值引用的形参,可以使用左值实参的,如上述示例。 示例-1修改后如下:

<示例-2>
template<typename T>
void print(T& t){
    cout << "lvalue" << endl;
}
template<typename T>
void print(T&& t){
    cout << "rvalue" << endl;
}
template<typename T>
void TestForward(T && v){
    cout << "TestForward(T && v)" << endl;
    print(v);
    print(std::forward<T>(v));
    print(std::move(v));
}
template<typename T>
void TestForward(T & v){
   cout << "TestForward(T & v)" << endl;
    print(v);
    print(std::forward<T>(v));
    print(std::move(v));
}
int main(){
    TestForward(1);
    int x = 1;
    TestForward(x);
    TestForward(std::forward<int>(x));
    return 0;
}
输出:
TestForward(T && v)
lvalue
rvalue
rvalue
TestForward(T & v)
lvalue
rvalue  //注意与示例-1的不同
rvalue
TestForward(T && v)
lvalue
rvalue
rvalue

【拓展:完美转发的引入②与引用折叠】

         完美转发适用于这样的场景:需要将一组参数原封不动的传递给另一个函数。

        “原封不动”不仅仅是参数的值不变,在 C++ 中,除了参数值之外,还有一下两组属性:左值/右值和 const/non-const。完美转发就是在参数传递过程中,所有这些属性和参数值都不能改变,同时不产生额外的开销,就好像转发者不存在一样。在泛型函数中,这样的需求非常普遍:

#include <iostream>
using namespace std;
template <typename T> void process_value(T & val)
{
    cout << "T &" << endl;
}
template <typename T> void process_value(const T & val)
{
    cout << "const T &" << endl;
}
//函数 forward_value 是一个泛型函数,它将一个参数传递给另一个函数 process_value
template <typename T> void forward_value(const T& val)
{
    process_value(val);
}
template <typename T> void forward_value(T& val)
{
    process_value(val);
}
int main()
{
    int a = 0;
    const int &b = 1;
    //函数 forward_value 为每一个参数必须重载两种类型,T& 和 const T&
    forward_value(a); // T&
    forward_value(b); // const T &
    forward_value(2); // const T&
    return 0;
}

        对于一个参数就要重载两次,也就是函数重载的次数和参数的个数是一个正比的关系。这个函数的定义次数对于程序员来说,是非常低效的。那C++11是如何解决完美转发的问题的呢?实际上,C++11是通过引入一条所谓“引用折叠”(reference collapsing)的新语言规则,并结合新的模板推导规则来完成完美转发。

typedef const int T;
typedef T & TR;
TR &v = 1; //在C++11中,一旦出现了这样的表达式,就会发生引用折叠,即将复杂的未知表达式折叠为已知的简单表达式

※一旦定义中出现了左值引用,引用折叠总是优先将其折叠为左值引用!!

// C++11中,std::forward可以保存参数的左值或右值特性,及const特性:
#include <iostream>
using namespace std;
template <typename T> void process_value(T & val)
{
    cout << "T &" << endl;
}
template <typename T> void process_value(T && val)
{
    cout << "T &&" << endl;
}
template <typename T> void process_value(const T & val)
{
    cout << "const T &" << endl;
}
template <typename T> void process_value(const T && val)
{
    cout << "const T &&" << endl;
}
//函数 forward_value 是一个泛型函数,它将一个参数传递给另一个函数 process_value
template <typename T> void forward_value(T && val) //参数为右值引用
{
    process_value( std::forward<T>(val) );//std::forward可以保存参数的左值或右值特性;  //val是转发者就像不存在一样,保持传入实参的属性
}
int main()
{
    int a = 0;
    const int &b = 1;
    forward_value(a); // T &
    forward_value(b); // const T &
    forward_value(2); // T &&
    forward_value( std::move(b) ); // const T &&
    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值