C/C++教程

C++右值引用

本文主要是介绍C++右值引用,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

 

左值、右值

1 左值是表达式结束后依然存在的持久对象

2 右值是表达式结束后不再存在的临时对象

简单来说,能取地址的是左值,否则就是右值。

 

右值引用的意义

实现移动语义完美转发

 

移动语义

C++11的右值引用和std::move可以实现移动语义,通过减少拷贝操作提升效率

class A{

public:
    A(): size(0), data(nullptr){}
    A(size_t size): size(size){
        data = new int[size];
    }

    A(const A& rhs){
        size = rhs.size;
        data = new int[size];
        for(int i=0; i<size; ++i){
            data[i] = rhs.data[i];
        }
        cout << "Copy constructor" << endl;
    }

    A& operator=(const A& rhs){

        if(this == &rhs){
            return *this;
        }

        delete[] data;

        size = rhs.size;
        data = new int[size];
        for(int i=0; i<size; ++i){
            data[i] = rhs.data[i];
        }
        cout << "Copy assignment" << endl;
        return *this;
    }


    ~A(){
        delete[] data;
    }

private:
    int *data;
    size_t size;
};

对于上面的类来说,无论是拷贝构造还是拷贝赋值,都需要将源数据一一拷贝,但是如果源数据在拷贝之后就不需要了,我们直接把源数据拿过来不是省事多了吗?对此我们只需要修改一下指针地址即可,在代码中加入下面的移动构造函数。

A(A&& rhs){
    data = rhs.data;
    size = rhs.size;
    rhs.size = 0;
    rhs.data = nullptr;
    cout << "Move constructor" << endl;
}

于是通过std::move将源数据转化为右值后就会调用相应的移动构造函数。

int main(){
    A a1 = A(10);
    A a2(std::move(a1));
}

 

完美转发

首先看第一个例子:

#include<iostream>
using namespace std;

int main(){
    int x = 5;
    int& left = x;
    int&& right = std::move(x);
    //cout << &left << endl << &right << endl;
}

在上面的代码中,被声明的左值引用和右值引用都是左值,是可以输出它们的地址的。

 

然后看二个例子:

#include<iostream>
using namespace std;

void func(int& x){
    cout << "左值" <<endl;
}

void func(int&& x){
    cout << "右值" <<endl;
}

void test(int&& x){
    func(x);
}

int main(){
    test(5);
}

在上面的代码中,最后的输出是左值,虽然传给 test 函数的是一个右值,然而在调用 func 时,使用的是 x,参数有了名称,联系第一个例子,此时 x 是一个左值。

如果想要最后的输出是右值,需要使用 std::forward,而 std::forward 中涉及到了引用折叠。

 

引用折叠

对于T&、T&&来说,其引用类型未定,可能是左值引用,也可能是右值引用,需要视传入的实参而定。

T& &  T& 左值引用的左值引用折叠为左值引用
T& && T& 右值引用的左值引用折叠为左值引用
T&& & T& 左值引用的右值引用折叠为左值引用
T&& && T&& 右值引用的右值引用折叠为右值引用

简而言之,只有两者均为右值引用时,最后的引用类型才为右值引用,否则均为左值引用

 

源码分析

 remove_reference 源码

template<typename _Tp>
struct remove_reference
{ typedef _Tp   type; };
 
// 特化版本
template<typename _Tp>
struct remove_reference<_Tp&>
{ typedef _Tp   type; };
 
template<typename _Tp>
struct remove_reference<_Tp&&>
{ typedef _Tp   type; };

std::forward 源码

template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }

根据上述源码,首先通过remove_reference获取 _Tp 的类型 type,然后声明左值引用变量 __t 。

根据 _Tp 的不同,_Tp 会发生引用折叠:

  1. 当 _Tp 为左值引用时,_Tp折叠为左值引用
  2. 当 _Tp 为右值引用时,_Tp折叠为右值引用

可以发现当 std::forward 的输入是左值引用时,输出也是左值引用;输入是右值引用时,输出也是右值引用。

 

在下面的代码中,使用了 std::forward 之后就会输出右值。

#include<iostream>
using namespace std;

void func(int& x){
    cout << "左值" <<endl;
}

void func(int&& x){
    cout << "右值" <<endl;
}

void test(int&& x){
    func(std::forward<int>(x));
}

int main(){
    test(5);
}

  

 

 

References:

  1. 一文带你详细介绍c++中的std::move函数
  2. C++ 理解std::forward完美转发
  3. 一文读懂C++右值引用和std::move
这篇关于C++右值引用的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!