一、移动语义
在前面的文章中对std::move进行了反复的分析,并对其背后的移动语义也进行了多视角的对比分析。比如右值引用,万能引用和完美转发等等。
对于很多人来说,其实对std::move与移动语义及std::move背后的行为并不甚了解。本文将尝试从不同的角度对std::move进行分析说明。
二、常见问题
先对移动语义和std::move的常见的细节问题进行一下说明,然后再进行分析。常见的有:
- std::move就是移动语义
这个肯定是不正确的,std::move主要是进行左、右值的转换,它会触发移动语义 - std::move会移动数据
这是最常见的理解错误,std::move只会将资源所有权转移而非拷贝。就像前面说的,只是把指针反向指向了另外一个所有者 - std::move移动后原对象消失
std::move移动后,原对象会进入一个有效但未指定状态,不应该再进行使用。简单理解成置空或消失并不准确 - 三五法则
这是一个很容易忽视的问题,在C++11后,除了构造函数以外的五个函数,只要有任何一定出现都必须定义其它几个。不定义行不行,行。但存在着隐性的问题 - 在返回值中滥用std::move
这点是没有把RVO和NRVO理解透彻或者说干脆忽视了它们。很多情况下,RVO更高效,而使用std::move则阻止了返回值优化
使用std::move移动的细节问题其实有很多,上面只是常见的几种,有兴趣的话可以多自己总结一下,这样会更好的加深对std::move和移动语义的理解。
三、理解和分析std::move
在了解了std::move的问题后,其实就很容易理解了其背后的机制。std::move负责触发移动语义,而真正的移动语义会定义在相关的移动构造函数和移动赋值函数。它只是一个类型转换接口(右值转右值引用),主要是为了防止深拷贝引起的性能下降。
其本质是一个“static_cast<typename std::remove_reference::type&&>(t)”,它不适合于小对象(深拷贝没影响)使用。const和本身就是右值的对象也不适用。其最适合应用的场景不是STL中的容器内对象的操作,可以大幅的减少拷贝的次数。比如常见的emplace_back中,就使用包括移动语义在内的提升性能的方法。
C++标准中对移动后原对象,使用的描述是“valid but unspecified”,这也是在上面提到时说单纯的指其为空或消失是不正确的原因所在。
在返回值的情况下,由于RVO的存在,一般是不建议使用std::move的,它会阻止返回值优化。这是得不偿失的。但在某些特定场景下,如返回成员变量、明确无法RVO以及调用已经明确包含移动结果的函数返回值等等。
来看一个简单的示意的例子:
#include <iostream>
#include <vector>
#include <string>
int main() {
std::vector<std::string> vecSrc = {"a", "aa", "aaa"};
std::vector<std::string> vecDest = std::move(vecSrc);
// 此时可以安全回收vecSrc的资源即vecSrc的析构函数会正常工作
// 重新赋值
vecSrc = {"b", "bb"}; // OK
// 调用状态无关的成员函数
std::cout << "vecSrc容量: " << vecSrc.capacity() << std::endl;
std::cout << "vecSrc是否为空: " << vecSrc.empty() << std::endl;
// 状态相关
// std::cout << vecSrc[0]<<std::endl; // 不确定,有风险
//std::cout << vecSrc.size()<<std::endl;//不保证size()一定是0
return 0;
}
四、总结
其实很多小细节只有在用到之后才可能体会到其的重要性。没有发生的事,对开发者来说往往只是一个故事,听听看看就罢了。这是人类的本能,无可厚非。但应用在技术上,还是尽量不要心存侥幸。

1059

被折叠的 条评论
为什么被折叠?



