左值(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. Windows堆管理机制 [1] 堆基础

    声明:这篇文章在写的时候,是最开始学习这个堆管理机制,所以写得有些重复和琐碎,基于笔记的目的想写得全一些,这篇文章写的时候参考了很多前辈的文章,已在末尾标出,某些未提及到的可以在评论补充 基于分享的目 ...

  2. LyScriptTools 扩展Script类API手册

    纯脚本类的功能实现都是调用的x64dbg命令,目前由于run_command_exec()命令无法返回参数,故通过中转eax寄存器实现了取值,目前只能取出整数类型的参数. 插件地址:https://g ...

  3. LLM面面观之RLHF平替算法DPO

    1. 背景 最近本qiang~老看到一些关于大语言模型的DPO.RLHF算法,但都有些云里雾里,因此静下心来收集资料.研读论文,并执行了下开源代码,以便加深印象. 此文是本qiang~针对大语言模型的 ...

  4. 在K8S中,集群可以做哪些优化?

    在Kubernetes(简称K8s)集群中,可以进行多种优化以提升性能.稳定性和资源利用率.以下是一些常见的优化措施: 控制面组件优化: kube-apiserver 高可用与扩展:通过配置多个API ...

  5. Delphi 官方 MD5

    去官方文档搜就行了,引入System.Hash 单元: http://docwiki.embarcadero.com/Libraries/Athens/en/System.Hash.THashMD5 ...

  6. Delphi 异常处理 详解

    [1] Exception类的定义在SysUtils单元中. [2] Delphi也支持不从Exception继承的异常类,但是我觉得这么做并不十分的明智. 一.异常的源 在Delphi的应用程序中, ...

  7. 五一不休息,每天都学习,从零教你手写节流throttle

    壹 ❀ 引 我在 从零教你手写实现一个防抖debounce方法 一文中详细的介绍了防抖概念,以及如何手写一个防抖.既然聊到防抖那自然避不开同等重要的节流throttle,老规矩,我们先阐述节流的概念, ...

  8. 【React】排查两小时,修改一个词,记一个因代码书写不规范导致的生命周期BUG

    壹 ❀ 引 因为现在工作主要以修bug为主,日常工作中总是会接触到千奇百怪的前端问题,它可能是代码缺陷导致的程序错误,也可能是方案不合理造成的性能问题,老实说修bug是一件很枯燥的事情,你需要阅读大量 ...

  9. JS Leetcode 154. 寻找旋转排序数组中的最小值 II 题解分析

    壹 ❀ 引 早在10个月前,也就是去年,我记录了JS leetcode 寻找旋转排序数组中的最小值 题解分析,你不得不了解的二分法一题,那么这篇文章记录它的升级版,来自LeetCode154. 寻找旋 ...

  10. useEffect与useLayoutEffect

    useEffect与useLayoutEffect useEffect与useLayoutEffect可以统称为Effect Hook,Effect Hook可以在函数组件中执行副作用操作,副作用是指 ...