# 移动语义有什么作用,原理是什么
面试官问"移动语义有什么作用,原理是什么",很多人能说出"避免深拷贝",但追问"std::move到底做了什么""移动后的对象还能用吗""编译器什么时候自动触发移动"就答不清楚了。这道题的核心是理解:移动语义把 O(n) 的深拷贝变成了 O(1) 的指针接管。
# 简要回答
移动语义让对象"搬家"而不是"克隆"。C++11 之前,把一个 vector<string> 传给函数或从函数返回,必须深拷贝所有元素——分配新内存、逐个复制、释放旧内存,代价是 O(n)。移动语义通过右值引用 T&& 识别出"这个对象马上要死了",然后移动构造函数直接把源对象的内部指针拿过来、把源对象的指针置空,整个过程只是两次指针赋值,O(1)。
# 详细回答
解决的问题
C++11 之前,临时对象(函数返回值、表达式中间结果)赋值给新变量时,必须走拷贝构造——分配新内存、逐字节复制、释放临时对象。对于管理堆内存的类(string、vector、自定义资源类),这个拷贝完全是浪费:临时对象马上就要析构了,它的内存完全可以直接"接管"过来。
移动语义就是为了解决这个问题:让编译器识别出"源对象即将销毁"的场景,触发移动构造而非拷贝构造,把资源转移而非复制。
实现原理
右值引用 T&& 是移动语义的语法基础。它只能绑定到右值(临时对象、将亡值),编译器看到参数类型是 T&& 就知道可以"偷"这个对象的资源。
移动构造函数的实现很简单:把源对象的指针拿过来,把源对象的指针置为 nullptr。两步完成,不分配新内存、不复制数据。移动赋值运算符类似,只是多了一步"先释放自己的旧资源"。
std::move 的本质
std::move(x) 等价于 static_cast<T&&>(x),它不移动任何数据,只是把左值的类型强转成右值引用,告诉编译器"这个对象我不要了,你可以搬走"。真正的资源转移发生在移动构造函数里。
移动后对象的状态
标准要求移动后的源对象处于"有效但未指定"状态——可以析构、可以赋新值,但不能假设它还有原来的数据。典型实现是把指针置 nullptr,这样析构时 delete nullptr 是安全的。
编译器自动触发移动的场景
函数返回局部对象时(RVO/NRVO 失败的情况下);vector 扩容搬元素时(前提是移动构造标了 noexcept);用临时对象初始化新对象时;push_back(临时对象) 时。

# 知识拓展
Q:为什么移动构造要加 noexcept?
vector 扩容时需要把旧元素搬到新内存。如果移动构造可能抛异常,搬到一半抛了,已经搬走的元素回不去了,数据就坏了。所以标准库看到移动构造没标 noexcept,就退回用拷贝构造来保证异常安全。加了 noexcept 才能享受移动带来的性能提升。
Q:什么时候该给类实现移动语义?
类管理了外部资源(堆内存、文件句柄、socket、锁)且拷贝代价高的时候。如果类只有几个 int 成员,移动和拷贝没区别,不需要专门写。unique_ptr 是典型例子——它禁止拷贝,只允许移动,因为资源所有权必须唯一。
Q:std::move 之后还能用源对象吗?
能用,但不能假设它的值。标准保证它处于"有效但未指定"状态,可以赋新值、可以析构,但不能读它的内容当作有意义的数据。实际工程中,move 之后最好不要再用源对象,除非是给它赋新值。
Q:移动语义和拷贝省略(RVO)是什么关系?
RVO 是编译器优化,直接在目标位置构造对象,连移动都省了——零开销。移动语义是 RVO 失败时的兜底方案。C++17 强制要求某些场景下 RVO,但函数有多个 return 路径时 RVO 可能失败,这时移动语义就起作用了。
评论
验证登录状态...