C++移动语义与传值:容器插入操作中两处std::move的必要性探究
std::move的必要性分析 你正在实现类似二叉搜索树的容器,要完成插入功能:容器有一个指向node结构体的根指针,node结构体包含addnode函数,目前的插入函数代码如下:
template<typename T> void container::insert(T val){ node* n = new node(std::move(val)); root->addnode(n); }
先提个小细节:你给出的node构造函数示例里,参数是T val,但初始化列表写的是value(std::move(item)),这里应该是value(std::move(val))吧?下面的分析会基于修正后的构造函数展开:
node(T val): value(std::move(val)) {}
你的问题是:这两处std::move是否均为必要?原因是什么?
直接给结论:两处std::move都是必要的,原因分别如下:
1. 插入函数中的std::move(val)
insert函数的参数val是一个左值(哪怕它是从调用方传入的右值移动构造而来,只要有变量名,它就是左值)。如果这里不用std::move,传给node构造函数的就是左值val,此时node构造函数的参数T val会触发T的拷贝构造函数——相当于把insert里的val完整拷贝一份给构造函数参数,带来不必要的性能开销。
而加上std::move(val)后,它会把左值val强制转换成右值引用,此时node构造函数的参数T val会触发T的移动构造函数(如果T支持移动语义的话),直接把insert里val的资源转移给构造函数参数,完全避免了拷贝操作,效率提升明显。
2. node构造函数中的std::move(val)
同理,构造函数的参数val也是一个左值(函数参数有名字,属于左值范畴)。如果不用std::move,初始化value成员时会调用T的拷贝构造函数,把参数val的内容拷贝到value中;加上std::move(val)后,会把左值参数转换成右值引用,触发T的移动构造函数,直接将参数val的资源转移到value里,再次规避了不必要的拷贝。
额外补充
如果T是不支持移动语义的类型(比如没有自定义移动构造函数,编译器也没生成默认版本),std::move也不会导致错误——此时它只是做了一次类型转换,最终还是会调用拷贝构造函数,不会影响代码正确性,只是没有效率提升而已。所以即使对于不可移动的类型,保留std::move也没有坏处。
内容的提问来源于stack exchange,提问作者LHC2012

