总而言之,右值引用,完美转发,std::move()都是为了在程序运行过程中,避免变量多次重复的申请和释放内存空间,使用移动语义将申请的空间通过这几种方式进行循环使用,避免重新开辟新空间和拷贝浪费算力。
右值引用
一、什么是右值引用?
在 C++ 中:
- 左值(Lvalue):有名字、有地址、可以被引用(如变量
x
) - 右值(Rvalue):临时对象、没有名字、无法被再次引用(如字面值
5
,表达式 x + y
)
语法:
右值引用使用 &&
定义。
二、为什么需要右值引用?
传统的 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); |
✅ 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); |
| wrapper(10); |
| } |
问题在于:模板参数 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) { |
| |
| |
| 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); |
| MyClass b; |
| MyClass* c = create<MyClass>(b); |
| MyClass* d = create<MyClass>(std::move(b)); |
| } |
std::move
🧠 一句话:
std::move()
是现代 C++ 中处理 右值引用、移动语义 的关键工具。std::move(obj)
并不会“移动”对象,而是把 obj
强制转换为右值引用,以便触发移动构造/移动赋值。是一个 类型转换工具函数。
一、std::move()
的作用
C++ 中,只有右值可以绑定到右值引用 T&&
。但是我们常常有一个左值变量,我们想“偷”它的资源(如在容器中或返回对象时)。这就需要:
二、std::move()
的常见用法
✅ 1. 移动构造 / 赋值
| Buffer b1; |
| Buffer b2 = std::move(b1); |
✅ 2. 函数返回值优化
| Buffer generateBuffer() { |
| Buffer temp; |
| return std::move(temp); |
| } |
✅ 3. 容器中移动元素
| std::vector<std::string> vec; |
| std::string str = "hello"; |
| |
| vec.push_back(std::move(str)); |
三、底层实现(源码原理)
| |
| 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); |
六、配套使用:std::move
vs std::forward
工具 | 用于哪里 | 行为 |
---|
std::move(obj) | 强制为右值 | 总是把 obj 转换成右值引用(T&& ) |
std::forward<T>(obj) | 完美转发(模板中) | 保留 obj 的左/右值本性(用于泛型转发) |
七、小结
✅ std::move()
本质是 static_cast<T&&>
✅ 用来启用移动语义,而不是实际移动
✅ 被移动的对象不能再继续用
✅ 与移动构造函数、移动赋值配合使用
原创作者: springcoming 转载于: https://www.cnblogs.com/springcoming/p/18868544