Как правильно определить конструктор перемещения?

Я искал в Интернете и нашел 3 способа определения конструктора перемещения:

  • Опираясь на компилятор:

    T(T&& other) = default;
    
  • Указатель разворота this:

    T(T&& other) {
      *this = std::move(other);
    }
    
  • Явно переназначить всех участников:

    T(T&& other) {
      T.a = other.a;
      T.b = other.b;
      //...
    }
    

Какой из них правильный? (И второй даже правильный?)

Ответ 1

Правильный общий способ состоит в том, чтобы перемещать-конструировать каждый элемент, но то, что в любом случае имеет версия с испражнением:

T(T && rhs)
: a(std::move(rhs.a))
, b(std::move(rhs.b))
{  }

В качестве грубого правила вы должны использовать определение по умолчанию, если это все, что вам нужно, и вы должны написать ex & shy; pli & shy; cit move constructor, если вы делаете что-то, что ex & shy; pli & shy; citly реализует семантику перемещения, такую как диспетчер ресурсов уникальной собственности:

URM(URM && rhs)
: resource(rhs.resource)
{
    rhs.resource = nullptr;
}

Показателем того, подходит ли это, вероятно, является ли ваш класс определяемым пользователем de & shy; struc & shy; tor. В этом примере деструктор освободит управляемый ресурс, и это должно произойти только один раз, поэтому объект с переместившимся объектом должен быть изменен.


Это не связано, но поскольку вы упоминаете оператор присваивания, здесь используется популярная именованная подкачка и назначение/своп:

void swap(URM & rhs) noexcept      // assume members are noexcept-swappable!
{
    using std::swap;
    swap(resource, rhs.resource);
    // ...
}

URM & operator=(URM rhs) noexcept  // pass by value
{
    rhs.swap(*this);
    return *this;
}

Красота этого подхода заключается в том, что вам нужна только одна версия оператора присваивания, которая работает как со временными, так и с не временными, используя при необходимости конструкцию перемещения, и пока все ваши члены хорошо разработаны, вы также нужна только одна функция swap. Кроме того, если функция подкачки не выбрасывает (которую должен разрешить хорошо продуманный класс), то ваш оператор присваивания не бросает, так как все возможные исключения будут уже встречаться на сайте вызова.

Ответ 2

T(T&& other)
: data(0) // initialize members
{
    swap(other); // where the class has a member function swap or std::swap(this->data, other.data)
}