Перемещение ctor и перемещение dtor - PullRequest
1 голос
/ 03 августа 2010

Как я спросил в Переместить конструктор / оператор = , и через некоторое время я согласился и принял правильный ответ на этот вопрос, я просто подумал, будет ли полезно иметь что-то вроде "движущийся деструктор" , который будет вызываться на перемещенном объекте каждый раз, когда мы использовали move ctor или operator =.
Таким образом, нам нужно будет указать только в Move dtor, что мы хотим от него и как наш объект должен бытьбыть аннулированным после использования конструктором перемещения.Без этой семантики похоже, что каждый раз, когда я пишу ctor или operator move, я должен в каждом из них явно указать (повторение кода / введение в ошибку), как обнулить перемещенный объект, что, на мой взгляд, не самый лучший вариант.Ждем ваших мнений на эту тему.

Ответы [ 3 ]

2 голосов
/ 03 августа 2010

Не могли бы вы привести конкретный пример, где это было бы полезно. Например, насколько я понимаю, назначение перемещения в общем случае может быть реализовано как

this->swap(rhv); 

Метод обмена, вероятно, полезен в любом случае, если класс извлекает выгоду из семантики перемещения. Это прекрасно делегирует работу по освобождению старых ресурсов *this обычному деструктору.

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

Также, в соответствии с последними ревизиями, конструктор перемещения / оператор присваивания может быть установлен по умолчанию. Это означает, что очень вероятно мои занятия будут выглядеть так:

class X
{
    well_behaved_raii_objects;
public:
    X(X&& ) = default;
    X& operator=(X&&) = default;
}; 

Никакого деструктора! Что могло бы заставить меня иметь в своем распоряжении два деструктора?

Также учтите, что у оператора присваивания есть старые ресурсы для работы. В соответствии с текущим стандартом, вы должны быть осторожны, что нормальный вызов деструктора в порядке как после конструирования и назначения, так и IMO, так же как и с предложенным деструктором перемещения, вы должны позаботиться в конструкторе и операторе присваивания, что один и тот же деструктор перемещения может быть безопасно позвонил. Или вы хотели бы два деструктора движения - по одному для каждого? :)


Переработанный пример msdn в комментариях с конструктором перемещения / назначением

#include <algorithm>

class MemoryBlock
{
public:

   // Simple constructor that initializes the resource.
   explicit MemoryBlock(size_t length)
      : length(length)
      , data(new int[length])
   {
   }

   // Destructor.
   ~MemoryBlock()
   {
      delete[] data; //checking for NULL is NOT necessary
   }

   // Copy constructor.
   MemoryBlock(const MemoryBlock& other)
      : length(other.length)
      , data(new int[other.length])
   {
      std::copy(other.data, other.data + length, data);
   }

   // Copy assignment operator (replaced with copy and swap idiom)
   MemoryBlock& operator=(MemoryBlock other)  //1. copy resource
   {
       swap(other);  //2. swap internals with the copy
       return *this; //3. the copy's destructor releases our old resources
   }

   //Move constructor
   //NB! C++0x also allows delegating constructors
   //alternative implementation:
   //delegate initialization to default constructor (if we had one), then swap with argument
   MemoryBlock(MemoryBlock&& other)
    : length(other.length)
    , data(other.data)
    {
        other.data = 0; //now other can be safely destroyed
        other.length = 0; //not really necessary, but let's be nice
    }

    MemoryBlock& operator=(MemoryBlock&& rhv)
    {
        swap(rhv);
        //rhv now contains previous contents of *this, but we don't care:
        //move assignment is supposed to "ruin" the right hand value anyway
        //it doesn't matter how it is "ruined", as long as it is in a valid state
        //not sure if self-assignment can happen here: if it turns out to be possible
        //a check might be necessary, or a different idiom (move-and-swap?!)
        return *this;
    }


   // Retrieves the length of the data resource.
   size_t Length() const
   {
      return length;
   }

   //added swap method (used for assignment, but recommended for such classes anyway)
   void swap(MemoryBlock& other) throw () //swapping a pointer and an int doesn't fail
   {
        std::swap(data, other.data);
        std::swap(length, other.length);
    }

private:
   size_t length; // The length of the resource.
   int* data; // The resource.
};

Некоторые комментарии к исходному образцу MSDN:

1) проверка на NULL до delete не нужна (возможно, это сделано для вывода, который я удалил, возможно, это указывает на недопонимание)

2) удаление ресурсов в операторе присваивания: дублирование кода. С идиомой копирования и замены удаление ранее удержанных ресурсов делегируется деструктору.

3) идиома копирования и обмена также делает ненужными проверки самоназначения. Это не проблема, если ресурс копируется до , он удаляется. - («Копировать ресурс независимо», с другой стороны, причиняет боль только тогда, когда вы ожидаете много самостоятельных заданий, выполненных с этим классом.)

4) В операторе присваивания в примере MSDN отсутствует какой-либо тип безопасности исключений: если при выделении нового хранилища происходит сбой, класс остается в недопустимом состоянии с недопустимым указателем. После уничтожения произойдет неопределенное поведение.

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

5) Проверка самопредставления выглядит особенно сомнительной в операторе присваивания. Я не вижу, как значение левой руки может быть таким же, как значение правой руки в первую очередь. Потребуется ли a = std::move(a);, чтобы достичь идентичности (похоже, это будет неопределенное поведение в любом случае?)?

6) Опять же, назначение перемещения - это ненужное управление ресурсами, которое моя версия просто делегирует обычному деструктору.

Вывод: дублирование кода, которое вы видите, можно избежать, оно вводится только наивной реализацией (которую вы по какой-то причине склонны видеть в руководствах, вероятно, потому, что код с дублированием проще для учеников).

Чтобы предотвратить утечку ресурсов, всегда бесплатно ресурсы (такие как память, файл ручки и розетки) в движении оператор присваивания.

... если редукция кода вас устраивает, в противном случае используйте деструктор повторно.

Для предотвращения невосстановимого уничтожение ресурсов, правильно справиться с самопредставлением в движении оператор присваивания.

... или убедитесь, что вы никогда не удаляете что-либо, прежде чем сможете его заменить. Или, скорее, вопрос SO: возможно ли самоопределение в случае назначения перемещения в четко определенной программе.


Кроме того, из моего черновика (3092) я обнаружил, что если в классе нет определенного пользователем конструктора / оператора копирования, и ничто не мешает существованию конструктора / назначения перемещения, то один будет неявно объявлен как дефолтный . Если я не ошибаюсь, это означает: если членами являются такие вещи, как строки, вектор, shared_ptrs и т. Д., И в этом случае вы обычно не пишете конструктор / назначение копирования, вы получите конструктор перемещения / назначение перемещения бесплатно .

0 голосов
/ 03 августа 2010

Конструктор / назначение перемещения - это место, где вы крадете ресурсы и оставляете состояние ресурсов украденного объекта в состоянии, которое обеспечивает безопасное уничтожение объекта при вызове его деструктора.Вы не можете увидеть или получить доступ к временному «значению», из которого вы украли ресурсы, кроме как в конструкторе / назначении перемещения.

В качестве примера, давайте возьмем строку.Вот где вы бы украли выделенные ресурсы и размер из временного объекта, и вы установили бы его значения на свои собственные значения (которые должны быть нулевыми и 0, если ваш объект был создан по умолчанию).

0 голосов
/ 03 августа 2010

Что не так с этим:

struct Object
{
  Object(Object&&o) { ...move stuff...; nullify_object(o); }
  Object & operator = (Object && o) { ...; nullify_object(o); }

  void nullify_object(Object && o);
};

Или альтернатива вызова nullify_object для цели: o.nullify();

Я не вижу большой выгоды от добавления YANLF.

...