std::move 与 std::forward 的区别?
核心区别
std::move:无条件把表达式转换为右值(T&&),表示“我不再需要它,可被移走”。它只是转换,不会真正移动。std::forward<T>:按原实参的值类别“条件转右值”。仅当原实参是右值且模板参数T不是左值引用时,才转成右值;否则保持为左值。用于“完美转发”。
何时使用
- 使用
std::move(你在当前处要“拿走资源”) - 容器插入/赋值:
vec.push_back(std::move(s)); - 成员初始化:
S(std::string s): s_(std::move(s)) {} -
接口按值接收再落地:
void set(std::string v){ value_ = std::move(v); } -
使用
std::forward(你只是“中转参数”,保留它原本是左值/右值) - 仅在“转发引用”模板中:
template<class T> void f(T&& x) { g(std::forward<T>(x)); } - 工厂/构造/
emplace参数传递:Ctor(Args&&... a): base_(std::forward<Args>(a)...) {}
返回值建议
- 返回局部对象按值:优先直接
return x;,让编译器做 NRVO/复制省略;不要强写std::move(x)(可能抑制 NRVO)。 - 返回成员/参数时需要明确移动:
return std::move(member_);
常见注意点
const+ move 通常退化为拷贝:const T被转成const T&&,多数类型的移动构造/赋值无法绑定到const,最终会复制。- 只在“转发引用”(
T&&且T参与类型推导)位置使用std::forward;若形参是T&/const T&或非模板,forward没意义。 - 移动后对象仍需保持“有效但未指定状态”;不要依赖其旧值,仅能析构或重新赋值。
- 有异常保证要求时,可考虑
std::move_if_noexcept:移动可能抛异常而拷贝不抛时选择拷贝。 - 右值一旦“起了名字”就成了左值表达式,若要继续按右值传递,需要
std::move/std::forward维持右值性。
最小示例(仅保留关键头文件)
#include <string>
#include <utility> // std::move, std::forward
// 用于区分左值/右值调用路径(空实现即可)
inline void f(const std::string&) {}
inline void f(std::string&&) {}
template<class T>
void forwarder(T&& x) {
// 完美转发:左值仍是左值,右值仍是右值
f(std::forward<T>(x));
}
struct Holder {
std::string m;
std::string take() { return std::move(m); } // 明确把成员移出
};
// 返回值:优先让编译器做 NRVO(不要强写 std::move)
inline std::string make() {
std::string t = "xyz";
return t; // NRVO/复制省略
}
inline void demo() {
// std::forward:仅在“转发引用”模板中使用
std::string a = "A";
forwarder(a); // 左值 -> f(const&)
forwarder(std::string("B")); // 右值 -> f(&&)
auto tmp = std::string("C");
forwarder(std::move(tmp)); // 命名对象右值化 -> f(&&)
// std::move:当下就要移交资源
std::string name = "alice";
f(std::move(name)); // name 进入“有效但未指定”状态
// const + move:通常退化为拷贝(多数类型对 const 不提供移动)
const std::string cs = "const";
f(std::move(cs)); // 调用 f(const&)(等价于拷贝语义)
}
一句话总结
你自己在当前语境要“拿走资源”时用 std::move;你只是中转参数、希望保留它原本是左值还是右值时用 std::forward<T>。