本文作者:StubbornHuang
版权声明:本文为站长原创文章,如果转载请注明原文链接!
原文标题:C++ – 左值和右值,右值引用与移动语义的概念与理解
原文链接:https://www.stubbornhuang.com/2424/
发布于:2022年11月28日 10:55:32
修改于:2022年11月28日 10:57:51

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
直接使用了。而我们之前提到的“移动语义”便可以简单地理解为将临时对象内已经分配好的内存区域直接“偷”过来使用这样一个过程。
总的来讲,使用基于右值引用的移动语义,可以使我们在复制具有大块内存空间的对象时可以直接使用原对象已经分配好的内存空间进而省去重新分配内存空间的过程,与拷贝操作相比,可以提升程序的执行效率。
参考连接
当前分类随机文章推荐
- C++ 11 - final关键字简要介绍 阅读1759次,点赞0次
- C++ - 导出接口函数和导出C++类 阅读26次,点赞0次
- C++11/std::thread - 可作为线程函数的几种方式总结 阅读3328次,点赞1次
- C++11/std::atomic - 原子变量(不加锁实现线程互斥) 阅读5852次,点赞2次
- C++STL容器 - std::map查找元素与判断键值是否存在方法总结 count,find,contains,equal_range,lower_bound,upper_bound 阅读787次,点赞0次
- C++ - 使用宏区分不同系统平台、不同编译器、不同编译模式等编译期宏使用总结 阅读1107次,点赞0次
- C++ - 在Windows/Linux上创建单级目录以及多级目录的跨平台方法 阅读783次,点赞0次
- Centos7 编译C++项目错误解决 : terminate called after throwing an instance of 'std::regex_error' 阅读2306次,点赞1次
- C++11 - 委托机制的实现TinyDelegate 阅读1201次,点赞0次
- C++ – 字节数组byte[]或者unsigned char[]与short的相互转换 阅读983次,点赞0次
全站随机文章推荐
- 资源分享 - 实时相机处理技术,Real-Time Cameras中文版PDF下载 阅读1217次,点赞0次
- Pytorch - torch.nn.Conv1d参数详解与使用 阅读1153次,点赞0次
- 资源分享 - Computer Graphics, C Version , Second Edition 英文高清PDF下载 阅读831次,点赞0次
- WordPress - 支持用户注册时使用中文名 阅读1766次,点赞0次
- Pytorch - 使用torch.matmul()替换torch.einsum('nctw,cd->ndtw',(a,b))算子模式 阅读1458次,点赞0次
- 资源分享 - C++Primer Plus,第6版,中文版,带书签超清 PDF下载 阅读1842次,点赞0次
- C++11 - 基于无锁队列的单生产者单消费者模型 阅读5390次,点赞1次
- 资源分享 - The HDRI Handbook 2.0- High Dynamic Range Imaging for Photographers and CG Artists高清PDF下载 阅读2113次,点赞0次
- Google Adsense - 从Google Adsense开通到第一个10美元我用了一年时间 阅读1825次,点赞2次
- ThreeJS - 设置透明背景模仿L2Dwidget.js看板娘渲染效果 阅读262次,点赞0次
评论
164