• 计算机图形学与计算几何经典必备书单整理,下载链接可参考:https://www.stubbornhuang.com/1256/

  • 本站由于前段时间遭受到大量临时和国外邮箱注册,所以对可注册的邮箱类型进行了限制!

  • 欢迎大家交换友链,可在https://www.stubbornhuang.com/申请友情链接进行友链交换申请!

  • 如果觉得本站的内容有帮助,可以考虑打赏博主哦!

  • 本站会放置Google广告用于维持域名以及网站服务器费用。

  • 感谢大家访问本站,希望本站的内容可以帮助到大家!

  • 问题反馈可发送邮件到stubbornhuang@qq.com

  • 在本站开通年度VIP,无限制下载本站资源和阅读本站文章

  • 工资「喂饱肚子」,副业「养活灵魂」!

C++ – 左值和右值,右值引用与移动语义的概念与理解

C++ 发布于2022-11-28 阅读 2,995次 0次评论 1次点赞 本文共2492个字,阅读需要7分钟。

1 C++左值右值

在看介绍C++11标准书籍的时候,经常在书中看到"左值"和"右值"的概念,这两个东西理解起来比较抽象。但是在C++11之后变得非常重要,其也是理解std::move()std::forward等新语义的基础。

int a = 100;

上述示例代码中,a为左值,100为右值。

1.1 C++左值和右值的基本概念

在C++中,我们需要用一个名字来表示内存中的一些东西(比如new一个数组时为数组起一个名字),这个名字我们经常称为"对象"。从上文中,对象表示一块连续的存储区域,而左值则表示指向对象的一条表达式。按照字面意思,左值表示可以用在赋值运算符左侧的东西(但是其实不是所有的左值都能用在赋值运算符的左侧,左值也有可能是一个常量),而为了补充和完善左值的含义,对应的右值诞生了,右值表示不能作为左值的值,比如函数返回的临时值,数值常量等。

1.2 判断左值还是右值的标准

当考虑对象的寻址,拷贝、移动等操作时,有两种属性非常关键:

  • 有身份:在程序中有对象的名字、指向该对象的指针,或者该对象的引用,我们就能判断两个对象是否相等或者对象的值是否发生了改变;
  • 可移动:能把对象的值移动出来(比如,我们能把它的值移动到其他的地方,剩下的对象处于合法但未指定的状态,与拷贝是有差别的);

从上面的两个属性进行判断,一个经典的左值是有身份的但是不能移动(因为我们可能会在移动后仍然使用它),一个经典的右值是允许执行移出操作的对像。

所以说:

  • 左值通常是可寻址的变量,生命周期具有持久性,不能被移动;
  • 而右值一般是不可寻址的变量,或者表达式中求值过程中创建的无名临时对象,其生命周期比较短暂,但是可以被移动;

1.3 左值引用和右值引用

左值引用和右值引用的概念如下:

  • 左值引用:引用一个对象;
  • 右值引用:必须绑定到右值的引用,C++11中右值引用可以实现移动语义,通过&&获得右值引用;
int a = 100;
int &y = x;

上述代码中,a为左值,100为右值,y为左值引用。

int a = 100;
int &y = x;
int &z = x * 100; // 错误,x * 100是一个右值,不能被引用
const int &z1 = x * 100; // 正确,可以将一个const引用绑定到一个右值上

int &&z2 = x * 100; // 正确,z2为右值引用
int &&z3 = x; // 错误,x是一个左值,不能作为右值引用

右值引用和相关的移动语义是C++11标准中引入的最强大的特性之一,通过std::move()可以避免无谓的复制,提高程序性能。

为什么这么说呢?

在C++11中,声明一个类时,除了构造函数和析构函数之外,我们经常还会声明拷贝构造函数、拷贝赋值函数、移动构造函数和移动赋值函数,比如看以下代码

#include <iostream>

class Vector
{
public:
    // 构造函数
    Vector(int length) :m_pData(new double[length]),m_DataSize(length)
    {
        for (int i = 0; i < length; ++i)
        {
            m_pData[i] = 0.0;
        }
    }

    // 析构函数
    virtual ~Vector()
    {
        delete[] m_pData;
    }

    // 拷贝构造函数
    Vector(const Vector& vec):m_pData(new double[m_DataSize]),m_DataSize(vec.m_DataSize)
    {
        for (int i = 0; i < m_DataSize; ++i)
        {
            m_pData[i] = vec.m_pData[i];
        }
    }

    // 拷贝赋值函数
    Vector& operator=(const Vector& vec)
    {
        double* temp = new double[vec.m_DataSize];
        for (int i = 0; i < vec.m_DataSize; ++i)
        {
            temp[i] = vec.m_pData[i];
        }

        delete[] m_pData; // 删除旧数据
        m_pData = temp;
        m_DataSize = vec.m_DataSize;

        return *this;
    }

    // 移动构造函数
    Vector(Vector&& vec):m_pData(vec.m_pData),m_DataSize(vec.m_DataSize)
    {
        vec.m_pData = nullptr;
        vec.m_DataSize = 0;
    }

    // 移动赋值函数
    Vector& operator=(Vector&& vec)
    {
        m_pData = vec.m_pData;
        m_DataSize = vec.m_DataSize;

        vec.m_pData = nullptr;
        vec.m_DataSize = 0;
    }


private:
    double* m_pData;
    int m_DataSize;
};

int main()
{
    return 0;
}

从上述代码中,我们在处理移动构造和移动赋值函数时,直接将右值引用对象vec的值直接赋值给了新的对象的值,这使得新对象可以直接使用右值引用对象内部已经分配好的这块内存空间,而不需要再像拷贝构造函数一样需要去重新分配内存空间。然后将临时值对象成员变量m_pData的值置为 nullptr 空指针,这样做的目的是为了防止临时值对象的析构函数在执行时将这块已经分配的内存区域清除,因为这快内存区域实际上已经被新对象中的成员变量m_pData直接使用了。而我们之前提到的“移动语义”便可以简单地理解为将临时对象内已经分配好的内存区域直接“偷”过来使用这样一个过程。

总的来讲,使用基于右值引用的移动语义,可以使我们在复制具有大块内存空间的对象时可以直接使用原对象已经分配好的内存空间进而省去重新分配内存空间的过程,与拷贝操作相比,可以提升程序的执行效率。

参考连接

欢迎扫码关注我的微信公众号,及时获取文章更新

微信公众号二维码

本文作者:StubbornHuang

版权声明:本文为站长原创文章,如果转载请注明原文链接!

原文标题:C++ – 左值和右值,右值引用与移动语义的概念与理解

原文链接:https://www.stubbornhuang.com/2424/

发布于:2022年11月28日 10:55:32

修改于:2023年06月21日 17:49:25

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

文章末尾
上一篇
资源分享 - 交互式计算机图形学:基于WebGL的自顶向下方法(第七版),Interactive Computer Graphics - A top-down approach with WebGL(Seven 7th Edition) 中文PDF下载
计算几何与计算机图形学资源
下一篇
Python - 安装onnxruntime-gpu出现ERROR: Could not install packages due to an OSError: [Errno 2] No such file or directory: '...\\numpy-1.23.1.dist-info\\METADATA'
Python
当前分类随机文章推荐

发表评论

您必须 [ 登录 ] 才能发表留言!

关注我们的公众号

微信公众号