引入

C++11引入了大量新特性,包括值类型的细分,以及右值引用,移动语义等等。包括std::move。

移动语义

移动语义,顾名思义,意为资源从一个对象移动到另一个对象,即资源所有权的转移,以避免拷贝时的性能开销,直接进行转移而不是复制。

设想有一个对象的生命周期即将结束(多为纯右值,亡值情况),而我们恰好又需要这部分资源,我们就可以调用移动构造,或移动赋值,将资源转移走,而不是赋值。

移动构造器

class A {
public:
//带参构造函数
explicit A(int val): resource(new int {val}) {}

//拷贝构造函数
A(const A& other) {
resource = new int {*other.resource};
}

//移动构造函数
A(A&& other) noexcept {
resource = other.resource;
other.resource = nullptr;
}

//析构函数
~A() {
if (resource) {
delete resource;
resource = nullptr;
}
}
private:
int* resource = nullptr;
};

移动赋值

class A {
public:
//带参构造函数
explicit A(int val): resource(new int {val}) {}

//(深)拷贝赋值
A& operator =(A& other) {
if (this != &other) {
delete resource;
resource = new int {*other.resource};
}
return *this;
}

//移动赋值
A& operator =(A&& other) noexcept {
delete resource;
resource = other.resource;
other.resource = nullptr;
return *this;
}

//析构函数
~A() {
if (resource) {
delete resource;
resource = nullptr;
}
}
private:
int* resource = nullptr;
};

移动构造/赋值使用场景

A retA(int num) {
return A(num*num);
}

int main() {
//由于涉及了返回值优化NRVO,实际上这里调用的是带参构造
//如果我们忽略NRVO,这里理应调用移动构造
A move_con1(retA(5));
A move_con2(A(6));

//调用了移动赋值
A move_assign1(7);
move_assign1 = retA(8);

//调用了移动赋值
A move_assign2(9);
move_assign2 = A(10);
}

std::move使用场景

以上场景都是针对纯右值/亡值而调用移动构造/赋值的情况,针对左值时,我们无法使用移动语义,所以引入了std::move,它可以将左值引用强制转换为右值引用以匹配移动语义。

源码剖析

两者都万变不离其宗,首先需要调用std::remove_reference<_Tp>::type用于移除_Tp的引用部分,然后再添加右值引用符号&&。这样,函数的返回类型就是一个右值引用。然后return static_cast<typename std::remove_reference<_Tp>::type&&>(__t);,它使用static_cast将参数__t转换为一个右值引用,并返回转换后的结果。其中move(_Tp&& __t)_Tp&&为万能引用。

GCC

template<typename _Tp>
_GLIBCXX_NODISCARD
constexpr typename std::remove_reference<_Tp>::type&&
move(_Tp&& __t) noexcept
{ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

MSVC

_EXPORT_STD template <class _Ty>
_NODISCARD _MSVC_INTRINSIC constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept {
return static_cast<remove_reference_t<_Ty>&&>(_Arg);
}

示例

//我们很容易写出如下的交换代码,但效率低下
void swapAAssign(A& a, A& b) {
A temp = a;
a = b;
b = temp;
}

//使用移动语义的交换
void swapAMove(A& a, A& b) {
A temp(std::move(a));
a = std::move(b);
b = std::move(temp);
}

int main() {
A a(5), b(6);

for (int i = 0; i < 1000000; ++i) {
swapAAssign(a, b);
}

for (int i = 0; i < 1000000; ++i) {
swapAMove(a, b);
}
}

swapAAssignswapAMove分别调用1000000次并记录运行时间:

swapAAssign: 543985 microseconds

swapAMove: 9966 microseconds

读者可自行验证