左值(Lvalue)与右值(Rvalue)

英文含义:

  • 左值(Lvalue)Locator value,意味着它指向一个具体的内存位置。

  • 右值(Rvalue)Read value,指的是可以读取的数据,但不一定指向一个固定的内存位置。

定义

  • 左值:指的是一个持久的内存地址。左值可以出现在赋值操作的左侧或右侧。例如,变量、数组的元素、对对象成员的引用等都是左值。
  • 右值:通常是临时的、不能有多个引用的值,它们不指向持久的内存地址。右值可以出现在赋值操作的右侧,但不能出现在左侧。字面量(如42、3.14)、临时对象、以及返回临时对象的表达式等都是右值。

完美转发(Perfect Forwarding)

完美转发是C++11引入的一个概念,其目的是允许函数模板将参数以原来的左值或右值的形式转发到其他函数。这是通过引用折叠规则std::forward函数实现的。完美转发的一个关键应用场景是模板函数中,我们希望将接收到的参数以完全相同的形式(保持其左值或右值性质)传递给另一个函数时使用。

引用折叠规则

在模板函数或类中,当一个引用的引用被形成时,它们会折叠成单一的引用

  • T& & ,T& &&, T&& & 都会被折叠为 T&
  • T&& && 会被折叠为T&&

示例

  • wrapper(lv)被调用时,lv是一个左值,因此模板参数T被推断为int&(左值引用)。由于引用折叠规则,T&&折叠为int&。因此,std::forward<T>(arg)arg作为左值引用转发给process函数,调用process(int& i)版本。
  • wrapper(20)被调用时,20是一个右值,因此模板参数T被推断为int。由于T是一个非引用类型,T&&就直接是int&&(右值引用)。因此,std::forward<T>(arg)arg作为右值引用转发给process函数,调用process(int&& i)版本。
#include <iostream>
#include <utility> // std::forward // 分别处理左值和右值
void process(int& i) {
std::cout << "Process left value: " << i << std::endl;
}
void process(int&& i) {
std::cout << "Process right value: " << i << std::endl;
} // 完美转发的模板函数
template<typename T>
void wrapper(T&& arg) {
// 使用std::forward来完美转发arg
process(std::forward<T>(arg));
} int main() {
int lv = 10; // 左值
wrapper(lv); // arg被推断为左值引用,因为lv是一个左值
wrapper(20); // 20是右值,arg被推断为右值引用
return 0;
}
/*
Process left value: 10
Process right value: 20
*/

转移(Move)

转移是指将一个对象的资源(如动态内存)从一个实例转移到另一个实例,而不是复制资源。这通常通过移动构造函数和移动赋值操作符实现,它们接受一个右值引用(Rvalue reference)作为参数。移动语义允许资源的高效转移,避免了不必要的复制,特别是对于大型对象或资源密集型对象。

使用std::move方法可以将左值转换为右值。使用这个函数并不能移动任何东西,而是和移动构造函数一样都具有移动语义,将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存拷贝。

从实现上讲,std::move基本等同于一个类型转换:static_cast<T&&>(lvalue);,函数原型如下:

template<class _Ty>
_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) _NOEXCEPT
{ // forward _Arg as movable
return (static_cast<remove_reference_t<_Ty>&&>(_Arg));
}

联系

  • 完美转发和移动语义都紧密依赖于左值和右值的概念。完美转发用于保持参数的左值或右值性质不变,而移动语义则是利用右值(通常是即将销毁的临时对象)来优化资源的管理。
  • 移动语义是完美转发常见的一个应用场景。当使用完美转发将函数参数传递给另一个函数时,如果该参数是一个临时对象(右值),则可以利用移动构造函数或移动赋值操作符,从而提高效率。
  • 右值的概念是转移语义的基础。只有右值(临时对象或显式标记为右值的对象)才能被移动,以此来优化资源的使用和提高程序的运行效率。

示例

以下是一个简单示例,其中包含一个自定义的String类,这个类通过实现移动构造函数和移动赋值操作符来优化内存资源管理。

同时,代码使用完美转发的函数模板,它可以根据传入参数的类型(左值或右值)来决定是否使用移动语义。

#include <iostream>
#include <cstring>
#include <utility> // std::move and std::forward class String {
private:
char* data;
size_t length; void freeData() {
delete[] data;
} public:
// 构造函数
String(const char* p = "") : length(strlen(p)), data(new char[length + 1]) {
std::copy(p, p + length + 1, data);
std::cout << "Constructed\n";
} // 析构函数
~String() {
freeData();
} // 拷贝构造函数
String(const String& other) : length(other.length), data(new char[length + 1]) {
std::copy(other.data, other.data + length + 1, data);
std::cout << "Copied\n";
} // 移动构造函数
String(String&& other) noexcept : data(other.data), length(other.length) {
other.data = nullptr;
other.length = 0;
std::cout << "Moved\n";
} // 移动语义的赋值运算符
String& operator=(String&& other) noexcept {
if (this != &other) {
freeData();
data = other.data;
length = other.length;
other.data = nullptr;
other.length = 0;
std::cout << "Move Assigned\n";
}
return *this;
} void print() const {
if (data) {
std::cout << data << std::endl;
}
}
}; // 完美转发示例
template<typename T>
void relay(T&& arg) {
// 使用 完全转发 来保持'arg'的左值/右值性质。
String temp(std::forward<T>(arg));
temp.print();
} int main() {
String s1("Hello");
String s2(std::move(s1)); // 调用移动构造函数 s1 = String("World"); // 移动语义赋值调用 String s3("Goodbye");
relay(s3); // 左值被传递
relay(String("Hello World")); // 右值被传递 return 0;
}
/*
Constructed
Moved
Constructed
Move Assigned
Constructed
Copied
Goodbye
Constructed
Moved
Hello World
*/

程序输出:

Constructed
Moved
Constructed
Move Assigned
Constructed
Copied
Goodbye
Constructed
Moved
Hello World

String类包含了移动构造函数和移动赋值操作符,当与右值交互时,可以有效地转移资源而不是进行复制。这样,当有一个临时的String对象时(例如在main函数中通过String("World")创建的临时对象),这个对象的资源可以被转移到另一个对象中而不需要额外的复制开销。

参考

C++左值右值完美转发转移的更多相关文章

  1. C++ 左值与右值 右值引用 引用折叠 => 完美转发

    左值与右值 什么是左值?什么是右值? 在C++里没有明确定义.看了几个版本,有名字的是左值,没名字的是右值.能被&取地址的是左值,不能被&取地址的是右值.而且左值与右值可以发生转换. ...

  2. C++ 左值 右值

    最近在研究C++ 左值 右值,搬运.收集了一些别人的资料,供自己记录和学习,若以后看到了更好的解释,会继续补充.(打“?”是我自己不明白的地方 )   参考:<Boost程序库探秘——深度解析C ...

  3. c++ 左值右值 函数模板

    1.先看一段代码,这就是一种函数模板的用法,但是红色的部分如果把a写成a++或者写成一个常量比如1,都是编译不过的,因为如果是a++的话,实际上首先是取得a的 值0,而0作为一个常量没有地址.写成1也 ...

  4. 左值&右值

    一.引子 我们所谓的左值.右值,正确的说法应该是左值表达式.右值表达式. 因为C++的表达式不是左值就是右值. 在C中,左值指的是既能够出现在等号左边也能出现在等号右边的表达式,右值指的则是只能出现在 ...

  5. C++11之右值引用(一):从左值右值到右值引用

    C++98中规定了左值和右值的概念,但是一般程序员不需要理解的过于深入,因为对于C++98,左值和右值的划分一般用处不大,但是到了C++11,它的重要性开始显现出来. C++98标准明确规定: 左值是 ...

  6. C语言几个术语: 数据对象,左值,右值

    1. 数据对象 赋值表达式语句的目的是把值存储到内存位置上. 用于存储值的数据存储区域统称为数据对象. 2. 左值 左值是C语言的术语, 用于标识特定数据对象的名称或表达式. 对象指的是实际的数据存储 ...

  7. i++和++i以及左值,右值

    左值(LValue)和右值(RValue)的一个快捷记法是赋值运算,左值是赋值运算左边的值,右值就是右边(=,=废话).例如: int a = 5; a就是左值,5就是右值. 当然,如果真是这么个含义 ...

  8. C和C指针小记(八)-操作符、左值右值

    1.移位操作符 移位操作符分为左移操作符(<<)和右移操纵符(>>) 对于无符号数:左右位移操作都是逻辑位移 对于有符号数:到底是采用逻辑位移还是算术位移取决于编译器.如果一个 ...

  9. [c++11]右值引用、移动语义和完美转发

    c++中引入了右值引用和移动语义,可以避免无谓的复制,提高程序性能.有点难理解,于是花时间整理一下自己的理解. 左值.右值 C++中所有的值都必然属于左值.右值二者之一.左值是指表达式结束后依然存在的 ...

  10. [转][c++11]我理解的右值引用、移动语义和完美转发

    c++中引入了右值引用和移动语义,可以避免无谓的复制,提高程序性能.有点难理解,于是花时间整理一下自己的理解. 左值.右值 C++中所有的值都必然属于左值.右值二者之一.左值是指表达式结束后依然存在的 ...

随机推荐

  1. TienChin 活动管理-搜索活动

    ActivityController @PreAuthorize("hasPermission('tienchin:activity:list')") @GetMapping(&q ...

  2. 重写JSON.stringify与JSON.parse使其支持解析function类型

    const JSONStringify = (option) => { return JSON.stringify(option, (key, val) => { // 处理函数丢失问题 ...

  3. Java注解之获取注解内部数据的原因分析

    我们都知道从JDK1.5开始,注解开始被支持使用,当我们在使用注解的时候感觉比配置文件用起来更加简便和清爽.配置文件是通过解析配置文件的内容获取到数据,那么为什么仅仅在类.方法或者属性上添加注解被注解 ...

  4. CF763E Timofey and our friends animals题解

    题目链接:CF 或者 洛谷 简单来说就是求 \([l,r]\) 这些点都存在的情况下,连通块的数量,看到七秒时限,而且每个点相连的边数很少,可以想到离线下来使用莫队类的算法解决 连通块问题,一般可以考 ...

  5. 教你用CSS实现表单部件

    案例介绍 欢迎来到我的小院,我是霍大侠,恭喜你今天又要进步一点点了!我们来用CSS编程实战案例,使用 列表标签完成一个下拉菜单样式的表单部件. 案例演示 运行代码后在浏览器弹出由 标签组成的下拉菜单样 ...

  6. .NET中使用BootstrapBlazor组件库Table实操篇

    前言 Table表格在后台管理应用中使用的是相当频繁的,因此找一个功能齐全的前端框架对于我们而言是非常必要的,因为封装完善的前端框架能够大大提升我们的工作对接效率.今天我们主要来讲解一下在.NET中使 ...

  7. Java 数字 默认是 Integer类型的问题,System.currentTimeMillis() + (180 * 24 * 60 * 60 * 1000)的问题,剖析、Long + Integer的问题

    最终结论: (180 * 24 * 60 * 60) 这种计算表达式在 Java中是默认以 Integer类型来的,若不超过 Integer的最大值则没有问题,若超过则必须用 (180 * 24 * ...

  8. Hadoop-Operation category READ is not supported in state standby 故障解决

    在查询hdfs时或者执行程序向hdfs写入数据时遇到报错:Operation category READ is not supported in state standby 意思是:该主机状态为待机, ...

  9. [Java]Java类中的各元素初始化顺序

    Java类中各元素的初始化顺序 初始化的原则是: 先初始化静态部分,再初始化动态部分:(先静再动) 先初始化父类部分,后初始化子类部分:(先父再子) 先初始化变量,次初始化代码块,再初始化构造器:(先 ...

  10. Matplotlib绘制散点图与条形图

    Matplotlib绘制散点图与条形图 绘制散点图 # 绘制散点图 from matplotlib import pyplot as plt from matplotlib import font_m ...