std::move做了什么
std::move不像它的名字会移动对象,而是更换所有权——将原指针置空,指针赋给新对象
MyString(MyString&& other) noexcept {
data_ = other.data_; // 赋指针
size_ = other.size_;
other.data_ = nullptr;
other.size_ = 0;
}
但以上代码并不是std::move干的活,具体而言,std::move仅仅做了类型转换,将左值换成右值引用,没有任何分配开销,而且更换所有权的操作是交给移动构造函数/移动赋值运算符
template<typename T>
typename std::remove_reference<T>::type&& move(T&& arg) noexcept
{
return static_cast<typename std::remove_reference<T>::type&&>(arg);
}
移动后的对象还能用吗?
移动后的对象处于有效但未指定的状态
std::string a = "hello world";
std::string b = std::move(a);
std::cout << "a = '" << a << "'" << std::endl; // 通常输出:a = ''
std::cout << "a.size() = " << a.size() << std::endl; // 通常输出:0
这意味着可以对它调用不依赖具体值的操作(如size()),但不能假定它的值是什么
std::move该什么时候用
- 明确不再需要某个对象
std::vector<std::string> names; std::string name = getUserInput(); names.push_back(std::move(name)); // name之后不再使用 - 在类的移动构造/赋值中转移成员
class Person { public: Person(Person&& other) noexcept : name_(std::move(other.name_)), address_(std::move(other.address_)) { } private: std::string name_; std::string address_; }; - swap
template<typename T> void mySwap(T& a, T& b) { T temp = std::move(a); a = std::move(b); b = std::move(temp); }
什么时候不能用std::move
- 对const对象std::move
对const对象std::move,静默退化为拷贝,且编辑器没有任何警告
-
return加std::move
return加std::move会阻止RVO优化(在调用方预留的内存空间里构造result,不需要任何拷贝或移动)
-
移动构造不加noexcept,容器扩容退化为拷贝
class MyBuffer { char* data_; size_t size_; public: // 移动构造函数,没加noexcept MyBuffer(MyBuffer&& other) : data_(other.data_), size_(other.size_) { other.data_ = nullptr; other.size_ = 0; } // ... }; std::vector<MyBuffer> buffers; buffers.reserve(2); buffers.emplace_back(/*...*/); buffers.emplace_back(/*...*/); buffers.emplace_back(/*...*/); // 触发扩容!扩容时如果移动构造函数没有加noexcept,会退化为拷贝,而不是移动
这是因为vector必须保证强异常安全,因为有可能搬运时会出问题,原本的数据必须是完好的
因此想让扩容时执行移动,就需要为移动构造函数加上noexcept
那么什么时候该加什么时候不该加呢?只要能保证它不抛异常(指针赋值和置空不会抛异常),就必须加noexcept
总结
std::move不移动对象,只是进行类型转换,将左值转换成右值引用;真正移动对象的是移动构造函数或移动赋值运算符
Reference
https://mp.weixin.qq.com/s/J8QPz6QDPSexWaBpYCVcCg




Comments | NOTHING