Перемещение ctor и перемещение dtor

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


person There is nothing we can do    schedule 03.08.2010    source источник


Ответы (3)


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

this->swap(rhv); 

Метод swap, вероятно, полезен в любом случае, если класс выигрывает от семантики перемещения. Это хорошо делегирует работу по освобождению старых ресурсов *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 и т. д., и в этом случае вы обычно не пишете конструктор/назначение копирования, вы получите конструктор перемещения/назначение перемещения бесплатно.

person Community    schedule 03.08.2010
comment
@ Дядя, не могли бы вы прикрепить ссылку, чтобы я мог об этом прочитать? Я действительно заинтересован в этом. Означает ли это, что согласно тому, что вы говорите, в dtor больше не будет необходимости? - person There is nothing we can do; 04.08.2010
comment
Вам нужна ссылка, в которой говорится, что class X {char* name; ... }; нужен деструктор, а class Y {string name; ...}; нет? - person UncleBens; 04.08.2010
comment
@UncleBens нет, я попросил ссылку на материал, в котором было бы объяснено, как по умолчанию будет работать cpy ctor или optor присваивания. Сказав это, то, что вы сказали в своем примере, означает, что, конечно, вам не нужен движущийся dtor, только если вы не используете ресурсы напрямую. В любом другом случае было бы полезно иметь move destructor так же, как полезно иметь dtor. - person There is nothing we can do; 04.08.2010
comment
Согласно моей версии черновика, версия по умолчанию будет просто выполнять перемещение по элементам (как при обычном копировании выполняется копирование по элементам). - Что касается случаев, когда ресурсы используются напрямую, то ваше предложение все же нуждается в мотивирующем примере. Прямо сейчас вы говорите, что это просто было бы полезно, и я говорю, что нетрудно представить простые и распространенные идиомы, которые не требуют никакого нового деструктора, точно так же, как идиома копирования-подкачки в текущем C++. - person UncleBens; 04.08.2010
comment
@UncleBens недостаточно мотивирует то, что вам нужно писать свой код только в одном месте (перемещение dtor), и вам не нужно беспокоиться о его явном вызове каждый раз, когда происходит семантика перемещения? Извините, но это почти те же самые причины, по которым у нас есть регулярный dtor. Или ты хочешь сказать, что без обычного доктора можно легко обойтись? - person There is nothing we can do; 04.08.2010
comment
@A-ha: И ИМО достаточно обычного dtor. Если буфер необходимо удалить, необходимо вызвать delete [] buffer;. Зачем мне два деструктора для этого? Например, если бы я написал класс String, выиграю ли я от вашего предложения и как? - person UncleBens; 04.08.2010
comment
@UncleBens Конечно, вы бы выиграли по тем простым причинам, которые я упомянул в своем последнем комментарии. Разве этого недостаточно, чтобы иметь безопасный код? - person There is nothing we can do; 04.08.2010
comment
@A-ha: Без примера я не могу понять этот аргумент. Мне кажется, что практически один и тот же код должен быть написан в двух местах (два деструктора). Ресурс должен быть освобожден (или нет — функции освобождения обычно могут обрабатывать NULL). Зачем мне для этого два разных деструктора? Что сделал бы движущийся деструктор для String? - person UncleBens; 04.08.2010
comment
@UncleBens, а не могли бы вы предоставить образец (но полностью работоспособный) движущегося ctor вашей строки? Если, например, в вашем движущемся ctor вы просто опорожняете или очищаете свой объект, из которого перемещаются ресурсы, но не уничтожаете его, тогда для этих операций будет использоваться перемещающийся dtor. Обратите внимание, что разрушения нет, потому что не используется настоящий dtor. Это просто подготовка объекта к состоянию, которое будет правильным и не вызовет ошибок, когда этот объект будет уничтожен реальным dtor. Как и в моем вопросе, к которому я прикрепил ссылку. - person There is nothing we can do; 05.08.2010
comment
продолжить в этом вопросе. Там, чтобы подготовить объект к уничтожению, я должен обнулить его, сбросив все его поля, и это работа для перемещения dtor. Никогда не пишется один и тот же код. Скажу даже больше: код пишется только один раз для каждой задачи, и вам не нужно беспокоиться о том, что вы забудете его вызвать. - person There is nothing we can do; 05.08.2010
comment
// Чтобы лучше увидеть этот код, проверьте ссылку в моем вопросе MemoryBlock(MemoryBlock&& other) : _data(NULL) , _length(0) { // Скопируйте указатель данных и его длину из // исходного объекта. _данные = другие._данные; _длина = другое._длина; // Освобождаем указатель данных от исходного объекта, чтобы // деструктор не освобождал память несколько раз. // Код ниже может быть и IMO должен быть в движении dtor other._data = NULL; другое._длина = 0; } - person There is nothing we can do; 05.08.2010
comment
продолжить вот ссылка на исходную статью: msdn.microsoft.com/en-us /библиотека/dd293665.aspx - person There is nothing we can do; 05.08.2010
comment
продолжить, и это строка из этой статьи: 3. Назначьте элементам данных исходного объекта значения по умолчанию. Это предотвращает многократное освобождение деструктором ресурсов (например, памяти). Я просто хочу обратить ваше внимание на предложение: Это не позволяет деструктору многократно освобождать ресурсы (например, память). И это для меня работа для движущегося dtor. Вместо того, чтобы каждый раз делать это вручную, вы пишете этот код в одном месте и не беспокоитесь о его вызове. Имеет ли это смысл для вас или все еще нет? - person There is nothing we can do; 05.08.2010
comment
продолжайте Иду спать, так что если я получу от вас ответ, я отвечу на него завтра. Спокойной ночи. - person There is nothing we can do; 05.08.2010
comment
@A-ha: обновленный ответ с анализом примера MSDN. Вывод: любая редупликация есть, потому что ее туда поставили. Дублирования можно избежать с помощью различных методов. - person UncleBens; 05.08.2010
comment
@UncleBens спасибо за ответ. Это немного прояснило в моей голове всю концепцию перемещения того и другого. Интересно, почему ради бога в MSDN показывают неправильно реализованный пример? Если вы пытаетесь учить кого-то, не указывайте ему неправильные пути в надежде, что их будет легче понять. Серьезно. - person There is nothing we can do; 05.08.2010
comment
@A-ha: Причина, по которой учебные пособия обычно реализуют такое назначение, может заключаться в том, что оно делает все шаги явными за счет повторного дублирования кода. В идиоме copy-n-swap все шаги более или менее неявны, и новичку может быть трудно понять, что происходит. - Это и, может быть, некоторые учебники от новичков к новичкам. - person UncleBens; 08.08.2010
comment
@UncleBens Я думаю, что если это правда, то они должны хотя бы указать, на каком уровне находится этот учебник. В противном случае, если я (любой пользователь) читаю с их веб-сайта, я считаю это наиболее подходящим способом, и похоже, что это не так, и это просто учебник для новичков. Как, черт возьми, я буду изучать правильную технику, если на таком сайте они показывают вещи для новичков, даже не говоря об этом. Сказав это, я хочу еще раз поблагодарить вас за вашу помощь. И кстати Майкрософт меня все больше бесит в последнее время. - person There is nothing we can do; 08.08.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.

person Edward Strange    schedule 03.08.2010
comment
@Noah да, но в вашем случае вы должны не забыть вызвать nullify_object. В моем предложении деструктор перемещения вызывается неявно. - person There is nothing we can do; 03.08.2010
comment
@Noah Я был бы признателен, если бы вы пояснили, что означает YANLF. - person There is nothing we can do; 03.08.2010
comment
еще одна новая языковая функция - person Edward Strange; 03.08.2010
comment
@Noah А что не так с новой языковой функцией, которая сделает нашу жизнь проще, а наш код безопаснее? - person There is nothing we can do; 03.08.2010
comment
Ничего такого. Но наступает момент, когда разработчик языка или комитет должны прекратить добавлять функции и просто выпустить язык, чтобы люди могли его реализовать. Поскольку предложенная вами функция очень и очень мало делает жизнь проще или безопаснее, было бы действительно глупо просто прикреплять ее к длинному списку вещей для работы с языком. В какой-то момент ответственностью разработчика становится убедиться, что он вызывает функции, необходимые для создания желаемого поведения. - person Edward Strange; 03.08.2010
comment
@Noah Я уверен, что люди говорили подобные вещи, когда dtor предлагался. И я согласен с тем, что в какой-то момент комитет должен прекратить добавлять функции в текущий стандарт. - person There is nothing we can do; 03.08.2010
comment
продолжить Но более чем очевидно, что будет еще один стандарт и будут добавлены другие функции. Я уверен, что перемещение dtor было бы более чем кстати для программистов, если бы оно было добавлено. Разве вы не предпочли бы писать свой код только в одном месте и быть уверенным, что этот код будет выполняться каждый раз, когда это необходимо, или вы предпочли бы писать один и тот же код много раз, и вы предпочли бы лично убедиться, что определенная функция вызывается каждый раз, когда это необходимо? - person There is nothing we can do; 03.08.2010
comment
Честно говоря, поскольку я регулярно использую RAII, я не видел необходимости делать что-то дополнительное, что могло бы войти в деструктор перемещения, несколько раз, когда я реализовывал функциональность перемещения. - person Edward Strange; 03.08.2010
comment
@Noah, так что вы говорите в своем движении ctor optor = вы не делали никакого дополнительного кода для очистки используемого объекта? Просто RAII было достаточно? - person There is nothing we can do; 04.08.2010

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

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

person David    schedule 03.08.2010
comment
не могу с вами согласиться по той причине, что если это вы проектируете класс, то вы видите и можете получить доступ к любому значению из вашего класса. Что касается вашего примера со строкой, вы не знаете, как дизайнеры этого класса справляются с движущейся семантикой, но они справились с этим и видели каждый бит этого класса. - person There is nothing we can do; 03.08.2010