C++中的左值,右值和移动语义详解

2023-12-26 05:09:56

左值,右值与移动语义

  • 左值表示了一个占据内存中某个可识别位置的对象。右值是无法寻址的临时对象或表达式的值。左值可以隐式地转换为右值,而右值不可。右值可以显式地赋值给左值。
  • 赋值运算符、取地址符、内置解引用运算符、下标运算符、内置类型和迭代器的递增递减运算符,都需要左值。
  • 右值引用与移动语义是C++11中引入的两个重要概念,它们旨在提高代码的性能,尤其是在涉及到资源管理和对象转移的场景中。

右值引用:

右值引用是对临时对象(即右值)的引用,它允许开发者安全地获取临时对象的资源。右值引用使用两个&&符号表示,例如int&&。它与左值引用(使用单个&符号,例如int&)不同,左值引用只能绑定到持久性对象上。右值引用使得编译器能够区分哪些对象是临时的,从而可以安全地转移其资源。

移动语义:

移动语义允许对象的资源(如动态分配的内存)被转移而非复制。这通常通过实现移动构造函数和移动赋值运算符来实现。当一个对象被移动时,它的资源被转移到新对象中,原对象则处于一个有效但不确定的状态,通常被设置为一个不再持有资源的状态。这意味着移动操作通常比复制操作更快,因为它避免了不必要的资源复制。以下例子来自于:

#include <iostream>
#include <utility>  // for std::swap

using std::cout;

class Intvec
{
public:
    explicit Intvec(size_t num = 0)
        : m_size(num), m_data(new int[m_size])
    {
        log("constructor");
    }

    ~Intvec()
    {
        log("destructor");
        delete[] m_data;
    }

    Intvec(const Intvec& other)
        : m_size(other.m_size), m_data(new int[m_size])
    {
        log("copy constructor");
        for (size_t i = 0; i < m_size; ++i)
            m_data[i] = other.m_data[i];
    }

    Intvec(Intvec&& other)
        : m_size(0), m_data(nullptr)
    {
        log("move constructor");
        // swap the members of this class with the members of other
        std::swap(m_size, other.m_size);
        std::swap(m_data, other.m_data);
    }

    Intvec& operator=(const Intvec& other)
    {
        log("copy assignment operator");
        Intvec tmp(other);
        std::swap(m_size, tmp.m_size);
        std::swap(m_data, tmp.m_data);
        return *this;
    }

    Intvec& operator=(Intvec&& other)
    {
        log("move assignment operator");
        std::swap(m_size, other.m_size);
        std::swap(m_data, other.m_data);
        return *this;
    }

private:
    void log(const char* msg)
    {
        cout << "[" << this << "] " << msg << "\n";
    }

    size_t m_size;
    int* m_data;
};

int main(){
    Intvec v1(20);
    Intvec v2;

    cout << "assigning lvalue...\n";
    v2 = v1;
    cout << "ended assigning lvalue...\n";

    cout << "assigning rvalue...\n";
    v2 = Intvec(33);
    cout << "ended assigning rvalue...\n";

    return 0;
}

运行结果:

[0x61ff00] constructor
[0x61fef8] constructor
assigning lvalue...
[0x61fef8] copy assignment operator
[0x61fec8] copy constructor
[0x61fec8] destructor
ended assigning lvalue...
assigning rvalue...
[0x61ff08] constructor
[0x61fef8] move assignment operator
[0x61ff08] destructor
ended assigning rvalue...
[0x61fef8] destructor
[0x61ff00] destructor

在main函数中,首先创建了一个Intvec对象v1,然后创建了另一个空的Intvec对象v2。接下来,执行了两次赋值操作:

  1. v2 = v1;:这是一个左值赋值,因为v1是一个左值。因此,调用了拷贝赋值运算符,创建了v1的一个副本给v2。
  2. v2 = Intvec(33);是一个右值赋值,因为Intvec(33)是一个临时对象,即一个右值。如果实现了移动赋值运算符,编译器会选择这个运算符来进行赋值,从而转移临时对象的资源给v2。如果没有实现移动赋值运算符,编译器会回退到使用拷贝赋值运算符,且v2
    =Intvec(33);的运行结果如下:
assigning rvalue...
[0x61ff08] constructor
[0x61fef8] copy assignment operator
[0x61fec8] copy constructor
[0x61fec8] destructor
[0x61ff08] destructor
ended assigning rvalue...

在第二次赋值操作中,如果实现了移动赋值运算符,临时对象的资源会被转移给v2,这通常比拷贝快得多,因为它避免了资源的复制。如果没有实现移动赋值运算符,则会调用拷贝赋值运算符,进行资源的复制。

同时可见,当同时存在拷贝赋值运算符(operator=)和移动赋值运算符(operator=)时,C++编译器会根据赋值操作的右侧操作数(即要赋值的对象)是左值还是右值来自动选择合适的赋值运算符。

文章来源:https://blog.csdn.net/qq_46348508/article/details/135211343
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。