C++左值右值完美转发转移
左值(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++左值右值完美转发转移的更多相关文章
- C++ 左值与右值 右值引用 引用折叠 => 完美转发
左值与右值 什么是左值?什么是右值? 在C++里没有明确定义.看了几个版本,有名字的是左值,没名字的是右值.能被&取地址的是左值,不能被&取地址的是右值.而且左值与右值可以发生转换. ...
- C++ 左值 右值
最近在研究C++ 左值 右值,搬运.收集了一些别人的资料,供自己记录和学习,若以后看到了更好的解释,会继续补充.(打“?”是我自己不明白的地方 ) 参考:<Boost程序库探秘——深度解析C ...
- c++ 左值右值 函数模板
1.先看一段代码,这就是一种函数模板的用法,但是红色的部分如果把a写成a++或者写成一个常量比如1,都是编译不过的,因为如果是a++的话,实际上首先是取得a的 值0,而0作为一个常量没有地址.写成1也 ...
- 左值&右值
一.引子 我们所谓的左值.右值,正确的说法应该是左值表达式.右值表达式. 因为C++的表达式不是左值就是右值. 在C中,左值指的是既能够出现在等号左边也能出现在等号右边的表达式,右值指的则是只能出现在 ...
- C++11之右值引用(一):从左值右值到右值引用
C++98中规定了左值和右值的概念,但是一般程序员不需要理解的过于深入,因为对于C++98,左值和右值的划分一般用处不大,但是到了C++11,它的重要性开始显现出来. C++98标准明确规定: 左值是 ...
- C语言几个术语: 数据对象,左值,右值
1. 数据对象 赋值表达式语句的目的是把值存储到内存位置上. 用于存储值的数据存储区域统称为数据对象. 2. 左值 左值是C语言的术语, 用于标识特定数据对象的名称或表达式. 对象指的是实际的数据存储 ...
- i++和++i以及左值,右值
左值(LValue)和右值(RValue)的一个快捷记法是赋值运算,左值是赋值运算左边的值,右值就是右边(=,=废话).例如: int a = 5; a就是左值,5就是右值. 当然,如果真是这么个含义 ...
- C和C指针小记(八)-操作符、左值右值
1.移位操作符 移位操作符分为左移操作符(<<)和右移操纵符(>>) 对于无符号数:左右位移操作都是逻辑位移 对于有符号数:到底是采用逻辑位移还是算术位移取决于编译器.如果一个 ...
- [c++11]右值引用、移动语义和完美转发
c++中引入了右值引用和移动语义,可以避免无谓的复制,提高程序性能.有点难理解,于是花时间整理一下自己的理解. 左值.右值 C++中所有的值都必然属于左值.右值二者之一.左值是指表达式结束后依然存在的 ...
- [转][c++11]我理解的右值引用、移动语义和完美转发
c++中引入了右值引用和移动语义,可以避免无谓的复制,提高程序性能.有点难理解,于是花时间整理一下自己的理解. 左值.右值 C++中所有的值都必然属于左值.右值二者之一.左值是指表达式结束后依然存在的 ...
随机推荐
- springboot整合nacos的入门Demo
Nacos介绍 Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称,一个更易于构建云原生应用的动态服务发现.配置管理和服务 ...
- 设计模式学习-使用go实现迭代器模式
迭代器模式 定义 优点 缺点 适用范围 代码实现 参考 迭代器模式 定义 迭代器模式(Iterator Design Pattern),也叫作游标模式(Cursor Design Pattern). ...
- HarmonyOS实战[一]——原理概念介绍安装:基础篇
[本文正在参与"有奖征文|HarmoneyOS征文大赛"活动] 相关文章: HarmonyOS实战[一]--原理概念介绍安装:基础篇 HarmonyOS实战[二]-超级详细的原子化 ...
- Linux的用户和权限 [补档-2023-07-07]
Linux用户和权限 3-1. su用户切换命令 exit用户退出命令 用户切换命令的语法: su [-] [用户名] 其中: - 可选,表示是否在切换用户后加载环境变量,建议带上. ...
- iOS 常用命令行工具总结
平时工作中会经常用到命令行工具Command Lines Tool.而Command Line Tool本质是一个命令行工具包,内部有很多有用的工具,如Apple LLVM compiler.Make ...
- 【树】N叉树的遍历【力扣589、力扣590】超详细的解释和注释
说在前面 欢迎朋友们来到我的博客. 今天我们的重点是,N叉树的遍历. 今天,博主就带来两道经典的题目,领着大家理解N叉树的前序遍历和后序遍历! 当然,还想学习其它算法的朋友们,可以通过订阅博主的算法专 ...
- 小知识:Flex ASM特性对集群资源显示的影响
有客户咨询,认为19c RAC集群资源状态和11g RAC大不一样,比如在他们的19c集群,也是只部署2节点,却显示3个资源状态,其中第三个还是offline状态,担心是否有影响. 实际上这和Flex ...
- DbgridEh 1900-01-01 00:00:00 问题解决
--------------------------------------------------
- Linux Shell 字符串截取方法
Linux 的字符串截取很有用.有八种方法. 假设有变量 var=http://www.aaa.com/123.htm. 1. # 号截取,删除左边字符,保留右边字符. 代码如下: echo ${va ...
- CF131D Subway 题解
题目传送门 前置知识 强连通分量 | 最短路 解法 考虑用 Tarjan 进行缩点,然后跑最短路. 缩点:本题的缩点有些特殊,基于有向图缩点修改而得,因为是无向图,所以在 Tarjan 过程中要额外记 ...