C++ 移动构造函数为什么设置noexcept?
答案显然是: 移动构造函数设置了noexcept
后STL的容器可以显著提高性能。
For example:
class MyClass {
public:
MyClass(int v) { val = v; }
MyClass(const MyClass& o) {
val = o.val;
std::cout << "Copy constructor " << val << std::endl;
}
MyClass(MyClass&& o) noexcept {
val = o.val;
std::cout << "Move constructor " << val << std::endl;
}
int val = 0;
};
int main() {
std::vector<MyClass> vec1;
vec1.push_back(MyClass(1));
std::cout << "insert second item:" << std::endl;
vec1.push_back(MyClass(2));
return 0;
}
猜测一下可能的流程:
- vec1没有reserve capacity,所以出事capacity是0(zero overhead)。
- 先push_back第一个元素,由于
MyClass(1)
是一个临时对象,所以调用移动构造函数。 - 再push_back第二个元素,这个时候超过了vec1的capacity,重新分配空间。
- 在重新分配的空间中,现在最后一个位置将
MyClass(2)
存进去,临时对象所以调用移动构造函数。 - 这是
MyClass(1)
还在原来的位置,所以需要移动到新的空间中,这时候就要决定是调用拷贝构造韩式移动构造,显然,移动构造效率更高。因此调用移动构造函数存入MyClass(1)
。 - vector在选择调用拷贝构造函数还是移动构造函数的时候会检查移动构造函数是否有
noexcept
,如果有则优先调用移动构造,否则调用拷贝构造函数
那为什么要看移动构造是不是 noexcept 呢,假设移动构造的内部实现可能会throw exception,那么 vector 在将旧元素移动到新空间时候,可能会触发 exception,考虑到数据的一致性,这个时候的预期结果应该是 push_back 失败,返回一个 error,然后 vector 原有数据保持不变,对吧? 但这个时候已经有一部分数据 move 到新空间了,原有数据已经被修改了,这个时候还怎么保持原有数据不变呢?
所以,使用 move 的前提就是保证(strong guarantee)移动构造不会 throw exception ,也就是 noexcept 关键字的作用,vector 看到 noexcept 就可以放心的使用 move 方案了,否则只能使用 copy 方案。
执行这个检查是 move_if_noexcept 函数,源码在这里:
template <class _Tp>
using __move_if_noexcept_result_t =
typename conditional<!is_nothrow_move_constructible<_Tp>::value && is_copy_constructible<_Tp>::value, const _Tp&,
_Tp&&>::type;
template <class _Tp>
_LIBCPP_NODISCARD_EXT inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 __move_if_noexcept_result_t<_Tp>
move_if_noexcept(_Tp& __x) _NOEXCEPT {
return _VSTD::move(__x);
}
最后给大家推荐一个LinuxC/C++高级架构系统教程的学习资源与课程,可以帮助你有方向、更细致地学习C/C++后端开发,具体内容请见 https://xxetb.xetslk.com/s/1o04uB