左值(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. 【二】MADDPG多智能体算法实现(parl)【追逐游戏复现】

    相关文章: [一]MADDPG-单智能体|多智能体总结(理论.算法) [二]MADDPG多智能体深度强化学习算法算法实现(parl)--[追逐游戏复现] 程序链接:直接fork:MADDPG多智能体深 ...

  2. 解决SpringMVC项目Jquery引入不生效问题

    根据多方查询,总结Jquery不生效问题如下几种原因: web.xml中拦截了静态资源,但是springmvc配置文件没有对静态资源访问进行设置 <!-- <mvc:resources l ...

  3. 给textarea添加行号,textarea使用代码风格的一些思考

    背景 项目有个需求是 在textarea中编辑脚本并显示为代码风格样式,显示行号: textarea显示行号 思路: 1.监听textarea内容变化,执行一个change函数,解析内容里面有多少个换 ...

  4. java判断系统时间是否在规定的时间区间之内

    java判断系统时间是否在规定的时间区间之内 1️⃣ 创建controller => service => serviceimpl package com.preciouslove.xin ...

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

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

  6. 【奶奶看了都会】云服务器部署开源ChatGLM-6B,让你也能拥有自己的ChatGPT

    1.背景 大家好啊,我是小卷. 最近ChatGPT不仅发布了GPT-4,而且解除封印可以联网了.不得不赞叹AI更新迭代的速度真快,都跟不上节奏了.但是大家也注意到了吧,随着ChatGPT的每次更新,O ...

  7. DNS转发器·

    实验介绍:dns转发器的作用 当客户端向dns1询问域名对应IP时,如果dns1不是该区域的授权域名服务器,则无法解析域名 dns1会根据转发器转发给dns3,如果dns3能解析,则返回给dns1. ...

  8. [JVM] Java内存分配

    Java内存分配 程序计数器 程序计数器是一块较小的内存区域,作用可以看做是当前线程执行的字节码的位置指示器.分支.循环.跳转.异常处理和线程恢复等基础功能都需要依赖这个计算器来完成. 虚拟机栈 虚拟 ...

  9. Python 中Time 模块

    python的time内置模块是一个与时间相关的内置模块,很多人喜欢用time.time()获取当前时间的时间戳,利用程序前后两个时间戳的差值计算程序的运行时间,如下: 1.使用time.time() ...

  10. Js模块化导入导出

    Js模块化导入导出 CommonJs.AMD.CMD.ES6都是用于模块化定义中使用的规范,其为了规范化模块的引入与处理模块之间的依赖关系以及解决命名冲突问题,并使用模块化方案来使复杂系统分解为代码结 ...