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++编译器会根据赋值操作的右侧操作数(即要赋值的对象)是左值还是右值来自动选择合适的赋值运算符。

相关推荐
程序员 沐阳11 分钟前
JavaScript 内存与引用:深究深浅拷贝、垃圾回收与 WeakMap/WeakSet
开发语言·javascript·ecmascript
&&Citrus31 分钟前
【CPN学习笔记(二)】Chap2 非分层颜色 Petri 网——从一个简单协议开始读懂 CPN
笔记·学习·php·cpn·petri网
Mr_Xuhhh1 小时前
Java泛型进阶:从基础到高级特性完全指南
开发语言·windows·python
HXQ_晴天1 小时前
Linux 磁盘清理 & 查看常用指令笔记
笔记
He1955011 小时前
wordpress搭建块
开发语言·wordpress·古腾堡·wordpress块
老天文学家了2 小时前
蓝桥杯备战Python
开发语言·python
赫瑞2 小时前
数据结构中的排列组合 —— Java实现
java·开发语言·数据结构
初夏睡觉2 小时前
c++1.3(变量与常量,简单数学运算详解),草稿公放
开发语言·c++
升职佳兴2 小时前
C盘爆满自救:3步无损迁移应用数据到E盘(含回滚)
c语言·开发语言
ID_180079054732 小时前
除了 Python,还有哪些语言可以解析 JSON 数据?
开发语言·python·json