移动语义可以使编译器使用移动操作来替换复制操作。移动构造函数和移动复制运算符具有控制对象移动的能力。std::move()可以实现移动语义,但是std::move()到底是怎么实现的呢?其底层原理是什么呢?
首先我们先观察一下std::move()的源码:
template<typename T>
decltype(auto) move(T&& t)noexcept {
using ReturnType = std::remove_reference_t<T>&&; //std::remove_reference_t 顾名思义去除T的引用性防止出现引用折叠
return static_cast<ReturnType>(t); //std::move()的实质性部分:强制类换为右值
}
-
我们发现std::move()没有进行任何的移动,没有生成任何的可执行代码。std::move只是仅仅执行了强制类型转换。std::move()无条件将实参转换成右值(临时变量和亡值)。在一个对象上实施std::move(),就是告诉编译器该对象具有可移动的操作。但是不是所以的对象都可以进行移动操作,就算使用std::move()也不一定进行移动操作。
class Test {
public:
Test() {};
Test(const Test&) {
std::cout << "拷贝构造" << std::endl;
}
Test(Test&&) {
std::cout << "移动构造" << std::endl;
}
};
class Weight {
public:
Weight(const Test _t) :t(std::move(_t)) {};
private:
Test t;
};
这时有人会问:为什么会调用构造函数呢?而不是移动构造函数呢?为什么是调用两次构造函数呢?
-
首先我们创建Test的对象传递给Weight。这时会进行值传递,创建一个临时变量,调用拷贝拷贝构造函数对临时变量进行构造。
-
第二次调用拷贝构造函数是:当我们使用std::move()时,实参传递给std::move()进行强转成右值,但是却保留了其常量性,所以std::move()的最终返回结果的型别时常量的右值引用,但是移动构造函数却要求非常量性,因此只能退而求其次,进行复制操作,进而调用构造函数。
因此,第一:不要将进行移动操作的对象声明成常量。第二:std::move()不仅不可以进行移动操作,而且不保证传入的对象具有可移动性。
std::move()可以实现对象的移动操作,虽然移动操作一般情况下比复制操作的效率要高,但是也存在一些情况下,移动操作的效率不是那么高。如下:
- 但待移动的对象未能提供移动操作,对象的移动操作会转变为进行对象的移动操作。
- 当待移动的操作由移动操作,但是效率却比复制操作低。
- 移动本可以发生的情况下,要求移动操作不可发射异常,但该操作未加上noexcept声明。