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


他们曾如此骄傲的活过,贯彻始终