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

左值,右值与移动语义

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

右值引用:

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

移动语义:

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

cpp 复制代码
#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;
}

运行结果:

cpp 复制代码
[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);的运行结果如下:
cpp 复制代码
assigning rvalue...
[0x61ff08] constructor
[0x61fef8] copy assignment operator
[0x61fec8] copy constructor
[0x61fec8] destructor
[0x61ff08] destructor
ended assigning rvalue...

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

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

相关推荐
汇能感知几秒前
光谱相机在AI眼镜领域中的应用
经验分享·笔记·科技
汇能感知几秒前
光谱相机的图像模式
经验分享·笔记·科技
努力努力再努力wz10 分钟前
【C++进阶系列】:万字详解红黑树(附模拟实现的源码)
java·linux·运维·c语言·开发语言·c++
cccyi715 分钟前
C/C++类型转换
c++
枫fengw17 分钟前
9.8 C++
开发语言·c++
王璐WL18 分钟前
【C语言入门级教学】内存函数
c语言·开发语言·算法
啃啃大瓜19 分钟前
python常量变量运算符
开发语言·python·算法
JCBP_1 小时前
QT(3)
开发语言·汇编·c++·qt·算法
秃顶老男孩.1 小时前
web中的循环遍历
开发语言·前端·javascript
XFF不秃头1 小时前
力扣刷题笔记-三数之和
c++·笔记·算法·leetcode