std::move的核心作用是将左值转换为右值引用,从而触发移动构造或移动赋值,避免昂贵的深拷贝。它本身不移动数据,而是通过类型转换通知编译器对象资源可被安全转移。真正执行移动的是类的移动构造函数或移动赋值运算符,它们窃取源对象资源并将其置空。使用std::move可显著提升性能的场景包括:容器中插入大对象、传递即将销毁的资源、实现高效swap等。但滥用会导致use-after-move错误、阻止RVO优化、对const对象无效或降低代码可读性,因此需谨慎使用。

C++中,
std::move
要使用
std::move
std::move
举个例子,假设你有一个自定义的
MyString
#include <iostream>
#include <vector>
#include <string>
#include <utility> // For std::move
// 一个简单的自定义类,展示移动语义
class MyResource {
public:
int* data;
size_t size;
MyResource(size_t s) : size(s) {
data = new int[size];
std::cout << "MyResource(size_t) - 构造 " << this << std::endl;
}
// 拷贝构造函数
MyResource(const MyResource& other) : size(other.size) {
data = new int[size];
std::copy(other.data, other.data + size, data);
std::cout << "MyResource(const MyResource&) - 拷贝构造 " << this << " 从 " << &other << std::endl;
}
// 移动构造函数
MyResource(MyResource&& other) noexcept : data(other.data), size(other.size) {
other.data = nullptr; // 将源对象置于有效但可析构的状态
other.size = 0;
std::cout << "MyResource(MyResource&&) - 移动构造 " << this << " 从 " << &other << std::endl;
}
// 拷贝赋值运算符
MyResource& operator=(const MyResource& other) {
if (this != &other) {
delete[] data;
size = other.size;
data = new int[size];
std::copy(other.data, other.data + size, data);
std::cout << "MyResource& operator=(const MyResource&) - 拷贝赋值 " << this << " 从 " << &other << std::endl;
}
return *this;
}
// 移动赋值运算符
MyResource& operator=(MyResource&& other) noexcept {
if (this != &other) {
delete[] data; // 释放当前资源
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
std::cout << "MyResource& operator=(MyResource&&) - 移动赋值 " << this << " 从 " << &other << std::endl;
}
return *this;
}
~MyResource() {
std::cout << "~MyResource() - 析构 " << this;
if (data) {
std::cout << " 释放资源";
delete[] data;
} else {
std::cout << " (无资源)";
}
std::cout << std::endl;
}
void print_status(const std::string& name) const {
std::cout << name << ": 地址=" << this << ", data=" << data << ", size=" << size << std::endl;
}
};
// 接受 MyResource 对象的函数
void process_resource(MyResource res) {
std::cout << " 进入 process_resource 函数" << std::endl;
res.print_status(" 函数内部res");
std::cout << " 离开 process_resource 函数" << std::endl;
}
int main() {
std::cout << "--- 场景1: 将临时对象传递给函数 (通常自动优化) ---" << std::endl;
process_resource(MyResource(100)); // 理论上会触发移动构造,或被RVO优化
std::cout << "\n--- 场景2: 显式使用 std::move 传递左值 ---" << std::endl;
MyResource r1(200);
r1.print_status("r1 (原始)");
process_resource(std::move(r1)); // 显式移动 r1
r1.print_status("r1 (移动后)"); // r1 处于有效但未指定状态
std::cout << "\n--- 场景3: 容器操作 ---" << std::endl;
std::vector<MyResource> resources;
MyResource r2(300);
resources.push_back(std::move(r2)); // 将 r2 移动到 vector 中
r2.print_status("r2 (移动到vector后)");
std::cout << "\n--- 场景4: 返回局部对象 (通常RVO/NRVO优化) ---" << std::endl;
auto create_and_return_resource = []() {
MyResource local_res(400);
std::cout << " create_and_return_resource 内部 local_res 地址: " << &local_res << std::endl;
return local_res; // 这里通常会触发RVO/NRVO,避免拷贝和移动
// 如果没有RVO/NRVO,则会触发移动构造
// return std::move(local_res); // 显式使用 std::move 可能阻止RVO,要小心
};
MyResource r3 = create_and_return_resource();
r3.print_status("r3 (从函数返回)");
std::cout << "\n--- main 函数结束 ---" << std::endl;
return 0;
}在上面的
main
std::move(r1)
r1
process_resource
res
r1
r1
data
立即学习“C++免费学习笔记(深入)”;
std::move
这是一个非常普遍的误解,也是我经常和同事朋友们聊到的话题。说实话,
std::move
static_cast<T&&>(lvalue)
想象一下,你有一个装满宝藏的箱子(
MyResource
std::move
所以,真正执行“移动”操作的是目标对象的移动构造函数或移动赋值运算符。它们接收一个右值引用,然后通常会:
如果没有为你的类定义移动构造函数或移动赋值运算符,那么
std::move
const T&
std::move
std::move
在我看来,
std::move
从函数返回大型局部对象: 虽然现代编译器通常会通过返回值优化(RVO)或具名返回值优化(NRVO)来消除这种拷贝,但并非所有情况都能优化。当RVO/NRVO不适用时(比如根据条件返回不同的局部对象),
std::move
return std::move(local_var);
return local_var;
在容器中存储或操作对象: 当你有一个已经存在的对象,想把它放入
std::vector
std::list
std::map
push_back(obj)
push_back(std::move(obj))
obj
std::vector<MyResource> resources; MyResource large_res(100000); // 一个很大的资源对象 resources.push_back(std::move(large_res)); // 移动而非拷贝
类似地,
std::map::insert
std::map::emplace
实现 std::swap
swap
std::vector
std::swap
MyResource rA(500), rB(600); // ... 对 rA 和 rB 进行一些操作 std::swap(rA, rB); // 内部会使用移动语义
传递参数给函数,且函数内部会“消耗”这个参数: 如果一个函数接受一个参数,并且它会在内部将其存储起来或者转移其所有权,那么使用
std::move
set_data
MyResource
class Widget {
MyResource m_res;
public:
void set_resource(MyResource res) { // 参数按值传递,这里会发生移动构造
m_res = std::move(res); // 移动赋值,将传入的临时对象移动到成员变量
}
};
// ...
MyResource temp_res(700);
Widget w;
w.set_resource(std::move(temp_res)); // 移动 temp_res 到 set_resource 的参数,再移动到 m_res这里
set_resource
std::move(res)
res
m_res
std::move
虽然
std::move
“Use-after-move”错误: 这是最常见也是最危险的陷阱。一旦你对一个对象使用了
std::move
std::move
std::string s1 = "Hello World"; std::string s2 = std::move(s1); std::cout << s1 << std::endl; // s1 的内容现在是未指定的,可能为空,也可能乱码,访问它很危险
正确的做法是,一旦对象被移动,就应该认为它已经“空了”或“失效了”,不再使用,除非你重新给它赋值。
阻止返回值优化(RVO/NRVO): 如前所述,当从函数返回一个局部变量时,编译器通常会自动进行RVO或NRVO,直接在调用者的内存空间构造对象,从而完全避免拷贝和移动。但如果你画蛇添足地写成
return std::move(local_variable);
return local_variable;
对 const
std::move
std::move
static_cast<T&&>(obj)
obj
const T
std::move(obj)
const T&&
const
const
const
const T&
std::move
const MyResource const_res(800); MyResource new_res = std::move(const_res); // 这里会调用拷贝构造函数,而非移动构造函数
对小对象或平凡类型使用 std::move
int
double
std::move
std::move
不必要的 std::move
std::move
std::move
总而言之,
std::move
以上就是C++如何使用std::move避免不必要拷贝的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号