C++ 右值引用 && 的用法

江枫雨发布

左值和右值

c++ 中值分为左值和右值,通常来讲左值定义位于等号左边,可以取地址或赋值给引用。右值位于等号右边,不能取地址,并且只能赋值给常引用。
比如:

int a = 1;

其中 a 是左值,1 是右值常量。
引用的使用方式:

int& b = a; // 正确
int& c = 1; // 编译错误,不能将右值赋值给引用(符合直觉,引用是变量的别名,可以改变变量值,如果是常量没有意义)。
const int& c = 1; // 正确,右值可以赋值给常引用。

左值引用和右值引用

左值引用 上面已经出现过,使用 & 符号定义:

int a = 1;
int& b = a;

右值引用 引用的是右值,使用 && 符号定义:

int a = 1;
int&& b = a; // 编译错误,左值不能赋给右值引用。
int&& b = 1; // 正确

如果想定义变量 a 的右值引用,可以用 std::move 或者 std:forward 函数,将变量 a 转化为右值,如下:

int a = 1;
int&& b = move(a); // 正确,move 函数将左值 a 转为右值。
int&& b = forward<int>(a); // 正确,forward 将左值转为右值。

std::forward<T>(arg) 有两个参数:T 与 arg,大致用法:

  1. 当 T 为左值引用类型时,arg 将被转换为 T 类型的左值。
  2. 否则 arg 将被转换为 T 类型右值。

右值引用的作用

c++11 引入右值引用,主要解决对象拷贝(比如动态分配的对象、指针指向的数据等)时避免深拷贝,将原有的动态数据移动到新的对象,提高运行效率。以下用一个自定义的 String 类拷贝为例(代码非原创,来自其它地方):

#include <iostream>
#include <string>
#include <cstring>
using namespace std;

class String
{
public:
    char* str;
    String() : str(new char[1])
    {
        cout << "调用默认空构造函数" << endl;
        str[0] = 0;
    }

    // 构造函数
    String(const char* s)
    {
        cout << "调用构造函数" << endl;
        int len = strlen(s) + 1;
        str = new char[len];
        strcpy_s(str, len, s);
    }

    // 复制构造函数
    String(const String & s)
    {
        cout << "调用复制构造函数" << endl;
        int len = strlen(s.str) + 1;
        str = new char[len];
        strcpy_s(str, len, s.str);
    }

    // 复制赋值运算符
    String & operator = (const String & s)
    {
        cout << "调用复制赋值运算符" << endl;
        if (str != s.str)
        {
            delete[] str;
            int len = strlen(s.str) + 1;
            str = new char[len];
            strcpy_s(str, len, s.str);
        }
        return *this;
    }

    // 移动构造函数
    // 和复制构造函数的区别在于,其参数是右值引用
    String(String && s) : str(s.str)
    {
        cout << "调用移动构造函数" << endl;
        // 清理原数据引用,防止多处引用同一数据,易出错。
        s.str = nullptr;
    }

    // 移动赋值运算符
    // 和复制赋值运算符的区别在于,其参数是右值引用
    String & operator = (String && s)
    {
        cout << "调用移动赋值运算符" << endl;
        if (str != s.str)
        {
            // 只复制了数据的指针地址
            str = s.str;
            // 清理原数据地址变量
            s.str = nullptr;
        }
        return *this;
    }

    // 析构函数
    ~String()
    {
        delete[] str;
    }
};

template <class T>
void MoveSwap(T & a, T & b)
{
    T tmp = move(a);  //std::move(a) 为右值,这里会调用移动构造函数
    a = move(b);  //move(b) 为右值,因此这里会调用移动赋值运算符
    b = move(tmp);  //move(tmp) 为右值,因此这里会调用移动赋值运算符
}

template <class T>
void Swap(T & a, T & b)
{
    T tmp = a;  //调用复制构造函数
    a = b;  //调用复制赋值运算符
    b = tmp;  //调用复制赋值运算符
}

int main()
{
    // 调用复制构造函数
    String s("hello");
    cout << "s: " << s.str << endl;

    // 调用空构造函数
    String s1;
    // 调用带参构造函数
    // 调用移动赋值函数,如果未定义移动赋值函数,则会调用普通赋值函数(引起深拷贝)。
    s1 = String("world");
    cout << "s1: " << s1.str << endl;

    // 调用移动构造函数
    String s2(move(s1));
    cout << "s2: " << s2.str << endl;

    // 调用移动赋值函数
    String s3;
    s3 = "hello";
    cout << "s3: " << s3.str << endl;

    // MoveSwap 相比 Swap 减少多次数据赋值
    cout << "===============测试 swap ================" << endl;
    String p1 = "hello", p2 = "world";
    cout << "=====复制 swap======" << endl;
    Swap(p1, p2);
    cout<< "=====移动 swap====" << endl;
    MoveSwap(p1, p2);

    system("pause");
    return 0;
}

输出结果:

调用构造函数
s: hello
调用默认空构造函数
调用构造函数
调用移动赋值运算符
s1: world
调用移动构造函数
s2: world
调用默认空构造函数
调用构造函数
调用移动赋值运算符
s3: hello
===============测试 swap ================
调用构造函数
调用构造函数
=====复制 swap======
调用复制构造函数
调用复制赋值运算符
调用复制赋值运算符
=====移动 swap====
调用移动构造函数
调用移动赋值运算符
调用移动赋值运算符

调用移动构造函数或移动赋值函数,避免了 String 类型中 str 成员的数据拷贝。

标准库 vector 中的右值引用

std::vector 的 push_back 函数使用了右值引用:

    void push_back(const _Ty& _Val) { // insert element at end, provide strong guarantee
        emplace_back(_Val);
    }

    void push_back(_Ty&& _Val) {
        // insert by moving into element at end, provide strong guarantee
        // 这里注意下,参数方式传入的 _Val 是右值引用,但作为变量它是左值,因而通过 move 函数转为右值。
        emplace_back(_STD move(_Val));
    }

        template <class... _Valty>
    _CONSTEXPR20 decltype(auto) emplace_back(_Valty&&... _Val) {
        // insert by perfectly forwarding into element at end, provide strong guarantee
        _Ty& _Result = _Emplace_one_at_back(_STD forward<_Valty>(_Val)...);
#if _HAS_CXX17
        return _Result;
#else // ^^^ _HAS_CXX17 ^^^ // vvv !_HAS_CXX17 vvv
        (void) _Result;
#endif // _HAS_CXX17
    }
分类: c++语言

0 条评论

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用*标注