C++11——右值引用&完美转发
总而言之,右值引用,完美转发,std::move()都是为了在程序运行过程中,避免变量多次重复的申请和释放内存空间,使用移动语义将申请的空间通过这几种方式进行循环使用,避免重新开辟新空间和拷贝浪费算力。
右值引用
一、什么是右值引用?
在 C++ 中:
- 左值(Lvalue):有名字、有地址、可以被引用(如变量
x) - 右值(Rvalue):临时对象、没有名字、无法被再次引用(如字面值
5,表达式x + y)
语法:
int&& r = 10; // r 是一个右值引用
右值引用使用 && 定义。
二、为什么需要右值引用?
传统的 C++(C++03)只有拷贝语义,会频繁复制对象,性能开销大。
右值引用的目的:
- 避免不必要的拷贝
- 支持移动语义
三、右值引用与移动构造函数
来看一个例子:
#include <iostream>
#include <vector>
using namespace std;
class Buffer {
public:
int* data;
size_t size;
Buffer(size_t s) : size(s) {
data = new int[s];
cout << "Constructor" << endl;
}
~Buffer() {
delete[] data;
cout << "Destructor" << endl;
}
// 拷贝构造函数
Buffer(const Buffer& other) : size(other.size) {
data = new int[size];
std::copy(other.data, other.data + size, data);
cout << "Copy Constructor" << endl;
}
// 移动构造函数
Buffer(Buffer&& other) noexcept : data(other.data), size(other.size) {
other.data = nullptr; // 接管资源
other.size = 0;
cout << "Move Constructor" << endl;
}
};
int main() {
Buffer b1(100);
Buffer b2 = std::move(b1); // 调用移动构造函数
}
输出:
Constructor
Move Constructor
Destructor
这里的 std::move 是 把左值转换成右值引用,以启用移动语义。
四、移动 vs 拷贝 的区别
- 拷贝构造函数:复制数据(深拷贝),两份资源。
- 移动构造函数:窃取资源指针,避免分配内存,效率更高。
五、右值引用的常见用法
1. 移动构造 / 移动赋值
Buffer(Buffer&& other); // 移动构造
Buffer& operator=(Buffer&& other); // 移动赋值
2. std::move 转换左值为右值引用
Buffer a(10);
Buffer b = std::move(a); // a 不再使用,资源移动给 b
3. 完美转发(在模板中)
template <typename T>
void wrapper(T&& arg) {
process(std::forward<T>(arg)); // 保留左/右值特性
}
六、右值引用 vs const 引用
| 特性 | const T& |
T&&(右值引用) |
|---|---|---|
| 是否可修改 | 否 | 可以(除非你加 const) |
| 是否绑定右值 | 可以 | 可以,仅右值 |
| 是否绑定左值 | 可以 | 不行 |
| 是否触发移动构造 | 不会 | 会 |
七、小结:右值引用是为“临时对象优化而生”
- 节省资源分配与拷贝成本(性能提升显著)
- 和
std::move、std::forward配合使用 - 支持自定义类的资源管理(RAII)更高效
完美转发
一、什么是完美转发?
完美转发的目标是:在模板中接收到参数后,不改变它的值类别(左值/右值)传递给其他函数。
举个问题:
你写了一个模板函数,想把参数“原封不动”地传给另一个函数,但:
void func(int& x) { cout << "Lvalue" << endl; }
void func(int&& x) { cout << "Rvalue" << endl; }
template<typename T>
void wrapper(T t) {
func(t); // 始终是左值,即使调用时是右值!
}
int main() {
int a = 5;
wrapper(a); // Lvalue
wrapper(10); // 还是 Lvalue
}
问题在于:模板参数 t 是一个左值变量,哪怕传进来的是右值,也会退化为左值。
二、解决方案:std::forward<T>(t)
template<typename T>
void wrapper(T&& t) {
func(std::forward<T>(t)); // 保留原始类型特性
}
️ 注意:
T&& t是 万能引用(Universal Reference),也叫转发引用(Forwarding Reference)std::forward<T>(t)是 完美转发 的关键,作用是:如果传进来是右值,就转发为右值;否则为左值
三、标准库中的使用案例:emplace_back
std::vector<std::string> vec;
vec.push_back("hello"); // 拷贝构造或移动构造
vec.emplace_back("hello"); // 直接构造在容器内部
emplace_back 原理(简化):
template<typename... Args>
void emplace_back(Args&&... args) {
// Args... 是参数类型包
// Args&&... 是万能引用
construct(std::forward<Args>(args)...); // 完美转发给构造函数
}
️ 这样就可以避免临时对象的生成,直接在容器内部原地构造,提高效率!
四、小结:完美转发的关键词
| 概念 | 说明 |
|---|---|
T&& 在模板中 |
是万能引用(不是右值引用) |
std::forward<T>(x) |
保持 x 原来的值类别(左值/右值) |
| 使用场景 | 构造函数转发、函数封装、容器的 emplace 系列等 |
五、一个完整的例子(构造任意类型)
#include <iostream>
#include <utility>
using namespace std;
class MyClass {
public:
MyClass(int x) { cout << "int ctor" << endl; }
MyClass(const MyClass& other) { cout << "copy ctor" << endl; }
MyClass(MyClass&& other) noexcept { cout << "move ctor" << endl; }
};
template<typename T, typename... Args>
T* create(Args&&... args) {
return new T(std::forward<Args>(args)...); // 完美转发构造对象
}
int main() {
MyClass* a = create<MyClass>(10); // 调用 int 构造函数
MyClass b;
MyClass* c = create<MyClass>(b); // 调用 copy ctor
MyClass* d = create<MyClass>(std::move(b)); // move ctor
}
std::move
一句话:
std::move()是现代 C++ 中处理 右值引用、移动语义 的关键工具。std::move(obj)并不会“移动”对象,而是把obj强制转换为右值引用,以便触发移动构造/移动赋值。是一个 类型转换工具函数。
一、std::move() 的作用
C++ 中,只有右值可以绑定到右值引用 T&&。但是我们常常有一个左值变量,我们想“偷”它的资源(如在容器中或返回对象时)。这就需要:
std::move(x); // 把 x 转成右值,让移动构造函数或移动赋值被调用
二、std::move() 的常见用法
1. 移动构造 / 赋值
Buffer b1;
Buffer b2 = std::move(b1); // 触发移动构造函数
2. 函数返回值优化
Buffer generateBuffer() {
Buffer temp;
return std::move(temp); // 可选,现代编译器可自动优化(NRVO)
}
3. 容器中移动元素
std::vector<std::string> vec;
std::string str = "hello";
vec.push_back(std::move(str)); // 移动 str,避免拷贝
三、底层实现(源码原理)
// 位于 <utility>
template<typename T>
typename std::remove_reference<T>::type&& move(T&& t) noexcept {
return static_cast<typename std::remove_reference<T>::type&&>(t);
}
解读:
T&& t是万能引用(可能是左值也可能是右值)remove_reference<T>::type去掉引用修饰- 最终执行一个
static_cast<T&&>—— 把值转换为右值引用类型
所以本质是:一个显式类型转换成右值引用的封装。
四、使用 std::move() 的注意事项 ️
| 场景 | 是否应该用 std::move? |
原因 |
|---|---|---|
| 变量将来还会使用 | 不推荐 | 被移动的对象通常处于“空壳”状态,后续使用容易出错 |
| 返回局部变量 | 可选 | 编译器可能已自动优化,但加 move 明确意图 |
| 函数参数是右值引用(T&&)变量 | 推荐 | 因为它是左值变量,仍需显式转换为右值 |
| const 对象 | 无意义 | const 对象不能移动,只能拷贝(移动构造需要非 const) |
五、例子:const 对象不能用 move
const std::string s = "hello";
std::string t = std::move(s); // 实际是拷贝,因为 s 是 const,不能 move
六、配套使用:std::move vs std::forward
| 工具 | 用于哪里 | 行为 |
|---|---|---|
std::move(obj) |
强制为右值 | 总是把 obj 转换成右值引用(T&&) |
std::forward<T>(obj) |
完美转发(模板中) | 保留 obj 的左/右值本性(用于泛型转发) |
七、小结
std::move() 本质是 static_cast<T&&>
用来启用移动语义,而不是实际移动
被移动的对象不能再继续用
与移动构造函数、移动赋值配合使用
C++11——右值引用&完美转发的更多相关文章
- C++11右值引用
[C++11右值引用] 1.什么是左值?什么是右值? 左值是表达式结束后依然存在的对象:右值是表达式结束时就不再存在的对象. 2.std::move的作用是什么? std::move用于把任意类型转化 ...
- 关于C++11右值引用和移动语义的探究
关于C++11右值引用和移动语义的探究
- [c++11]右值引用、移动语义和完美转发
c++中引入了右值引用和移动语义,可以避免无谓的复制,提高程序性能.有点难理解,于是花时间整理一下自己的理解. 左值.右值 C++中所有的值都必然属于左值.右值二者之一.左值是指表达式结束后依然存在的 ...
- C++11右值引用和std::move语句实例解析
关键字:C++11,右值引用,rvalue,std::move,VS 2015 OS:Windows 10 右值引用(及其支持的Move语意和完美转发)是C++0x将要加入的最重大语言特性之一.从实践 ...
- C++11 右值引用和转移语义
新特性的目的 右值引用 (Rvalue Referene) 是 C++ 新标准 (C++11, 11 代表 2011 年 ) 中引入的新特性 , 它实现了转移语义 (Move Sementics) 和 ...
- C++11 右值引用 与 转移语义
新特性的目的 右值引用(R-value Reference)是C++新标准(C++11, 11代表2011年)中引入的新特性,它实现了转移语义(Move Semantics)和精确传递(Perfect ...
- [C++]C++11右值引用
右值引用的概念(摘自C++Primer) 左值和右值的概念 1.左值和右值是表达式的属性,一些表达式要求生成左值,一些表达式要求生成右值:左值表达式通常是一个对象的身份,而一个右值表达式表示的是对象的 ...
- C++ 11 右值引用
C++11中引入的一个非常重要的概念就是右值引用.理解右值引用是学习“移动语义”(move semantics)的基础.而要理解右值引用,就必须先区分左值与右值. 注意:左值右值翻译可能有些问题 *L ...
- C++ 11 右值引用以及std::move
转载请注明出处:http://blog.csdn.net/luotuo44/article/details/46779063 新类型: int和int&是什么?都是类型.int是整数类型,in ...
- 【转】C++ 11 右值引用的理解
右值引用的目的之一,是为了C++中一个比较影响性能的问题:拷贝临时对象,例如,在 int foo(){ ... } int x; x = foo(); 中,在第三句中,发生了以下的事情: 1.销毁 x ...
随机推荐
- 数据团队必读:智能数据分析文档(DataV Note)五种高效工作模式
数据项目,无论是数据分析.可视化,还是数据科学和机器学习相关的项目,通常都非常复杂,涉及多个组成部分,比如代码.数据.运行环境.SQL脚本以及分析报告等:与此同时,随着AI时代的到来,数据科学领域正经 ...
- autohue.js:让你的图片和背景融为一体,绝了!
需求 先来看这样一个场景,拿一个网站举例 这里有一个常见的网站 banner 图容器,大小为为 1910*560 ,看起来背景图完美的充满了宽度,但是图片原始大小时,却是: 它的宽度只有 1440 , ...
- element-ui实现table表格的嵌套(table表格嵌套)功能实现
最近在做电商类型的官网,希望实现的布局如下:有表头和表身,所以我首先想到的就是table表格组件. 表格组件中常见的就是:标题和内容一一对应:像效果图中的效果,只用基础的表格布局是不行的,因此我想 ...
- java http协议,添加header以及post传参,以及服务端获取
一.客户端请求 public static String test(){ JSONObject obj = new JSONObject(); obj.put("cusName", ...
- mysql常用优化
SQL优化是一个分析,优化,再分析,再优化的过程.站在执行计划的角度来说,我们这个过程,就是在不断的减少rows的数量. 1.建索引 2.减少表之间的关联 3.优化 sql,尽量让 sql 很快定位数 ...
- CF2043C Sums on Segments
题意概要 一个数组,最多有一个数的绝对值不是 \(1\),求出所有可以得到的区间和. 思路 这里提供一个 数据结构优化查询前缀和最值 的做法. 最多有一个数的绝对值不是 \(1\),那我们可以先忽略掉 ...
- Python装饰器:套层壳我变得更强了!
Python装饰器:套层壳我变得更强了 Python装饰器:套层壳我变得更强了 关于作用域和闭包可以聊点什么? 什么是作用域 什么是闭包 装饰器:套层壳我变得更强了 参考资料 昨天阅读了<Pyt ...
- 分布式锁—5.Redisson的读写锁
大纲 1.Redisson读写锁RedissonReadWriteLock概述 2.读锁RedissonReadLock的获取读锁逻辑 3.写锁RedissonWriteLock的获取写锁逻辑 4.读 ...
- labelImg 工具介绍
1.什么是labelImg 图片标记工具,生成的xml文件用于人工智能数据 2.怎么使用 打开图片文件夹 使用默认工具tooth 选中图片,快捷键w ,鼠标标记图片 [Ctrl+s] 保存
- Manjora配置记录
22/9/12 目前的启动项有3:Windows Boot Manager.Manjaro.UEFI OS.其中UEFI OS 和 Manjaro 进入后内容相同:Windows下检测不到Manjar ...