c++移动语义、万能引用、引用折叠、完美转发

想了解标题的内容,我们可以先简单了解下c++中的左值和右值。

左值和右值

  • 左值:能对表达式取地址,就是左值
  • 右值:不能对表达式取地址,就是右值
  • 将亡值:和右值引用相关的表达式,这样的表达式通常是将要移动的对象、T&&函数返回值、std::move()函数的返回值等
  • 纯右值:就是c++98标准右值的概念

这里可以把将亡值和纯右值都看作右值即可。

左值引用和右值引用

  • 左值引用
    如下:
int a = 1;
int& ref = a; //ref 就是a的别名,a是左值,ref就是左值引用
  • 右值引用
    右值引用的符号是&&,如下:
int&& a = 1; //这里1是右值,所以a就是右值引用

这里需要注意的是:左值引用只能绑定左值,右值引用只能绑定右值。但是常量左值const T&,既可以绑定左值又可以绑定右值。

移动构造和移动赋值

举个简单例子:

class MyString{
public:
    MyString(): data_(nullptr), length_(0){
    }
    
    ~MyString(){
        if (nullptr != data_) {
            delete[] data_;
            data_ = nullptr;
        }
    }
    
    // 移动构造
    MyString(MyString&& str) {
        data_ = str.data_;
        length_ = str.length_;
        str.data_ = nullptr;
        str.length_ = 0;
    }
    
    // 移动赋值函数
    MyString& operator=(MyString&& str) {
        // 避免自我赋值
        if (this == &str) {
            return *this;
        }
        
        delete[] data_;
        data_ = str.data_;
        str.data_ = nullptr;
        return *this;
    }
private:
    char* data_;
    int length_;
};

万能引用(也叫通用引用)

先看个例子:

template
void func(T& t) {
    cout << t << endl;
}

int main() 
{
    int a = 10;
    func(a); // 正常,传入的是左值
    func(10); // 报错,参数是个左值,但是传入的10是右值
    return 0;
}

C++ 11中有万能引用(Universal Reference)的概念:使用T&&类型的形参既能绑定右值,又能绑定左值。

但是注意了:只有发生类型推导的时候,T&&才表示万能引用;否则,表示右值引用。

所以上面的可以修改为:

template
void func(T&& t) {
    cout<< t << endl;
}

int main() 
{
    int a = 10;
    func(a); 
    func(10);
    return 0; 
}

引用折叠

一个模板函数,根据定义的形参和传入的实参的类型,我们可以有下面四中组合:

  • 左值-左值 T& & # 函数定义的形参类型是左值引用,传入的实参是左值引用
  • 左值-右值 T& && # 函数定义的形参类型是左值引用,传入的实参是右值引用
  • 右值-左值 T&& & # 函数定义的形参类型是右值引用,传入的实参是左值引用
  • 右值-右值 T&& && # 函数定义的形参类型是右值引用,传入的实参是右值引用

但是C++中不允许对引用再进行引用,对于上述情况的处理有如下的规则:

所有的折叠引用最终都代表一个引用,要么是左值引用,要么是右值引用。

规则是:如果任一引用为左值引用,则结果为左值引用。否则(即两个都是右值引用),结果为右值引用。

即就是前面三种情况代表的都是左值引用,而第四种代表的右值引用。

完美转发

老规矩,先看例子:

template
void func(T& t) {
    cout << "左值" << endl;
}

template
void func(T&& t) {
    cout << "右值" << endl;
}

template
void Test(T&& t) {
    func(t);
}

int main() 
{
    int a = 10;
    Test(a);
    Test(10);
    return 0;    
}

输出结果:

左值
左值

这里就已经和预期的不一样了,预期应该第二个输出右值。

Test()函数本身的形参是一个万能引用,即可以接受左值又可以接受右值;第一个Test()函数调用实参是左值,所以,Test()函数中调用func()中传入的参数也应该是左值;第二个Test()函数调用实参是右值,根据上面所说的引用折叠规则,Test()函数接收的参数类型是右值引用,那么为什么却调用了调用func()的左值版本了呢?这是因为在Test()函数内部,左值引用类型变为了右值,因为参数有了名称,我们也通过变量名取得变量地址。

那么问题来了,怎么保持函数调用过程中,变量类型的不变呢?这就是我们所谓的“完美转发”技术,在C++11中通过std::forward()函数来实现。我们修改我们的Test()函数如下:

template
void func(T& t) {
    cout << "左值" << endl;
}

template
void func(T&& t) {
    cout << "右值" << endl;
}

template
void Test(T&& t) {
    func(std::forward(t));
}

int main()
{
    int a = 10;
    Test(a);
    Test(10);
    return 0;
}

输出结果:

左值
右值

可以输出预期的结果。

你可能感兴趣的:(c++,开发语言,后端)