Как я уже просил в Переместить конструктор/оператор=, и через некоторое время я согласился и принял правильное ответ на этот вопрос. Я только что подумал, было бы полезно иметь что-то вроде "Moving Destructor", который вызывался бы для перемещаемого объекта каждый раз, когда мы использовали команду перемещения или оператор =.
В этом Таким образом, нам пришлось бы указывать только в move dtor, что мы хотим от него и как наш объект должен быть аннулирован после использования конструктором перемещения. Без этой семантики каждый раз, когда я пишу move ctor или operator=, я должен явно указывать в каждом из них (повторение кода/введение ошибки), как аннулировать перемещаемый объект, что, я думаю, не лучший вариант. Жду ваших мнений на эту тему.
Перемещение ctor и перемещение dtor
Ответы (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 и т. д., и в этом случае вы обычно не пишете конструктор/назначение копирования, вы получите конструктор перемещения/назначение перемещения бесплатно.
class X {char* name; ... };
нужен деструктор, а class Y {string name; ...};
нет?
- person UncleBens; 04.08.2010
delete [] buffer;
. Зачем мне два деструктора для этого? Например, если бы я написал класс String, выиграю ли я от вашего предложения и как?
- person UncleBens; 04.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.
Конструктор/назначение перемещения — это место, где вы крадете ресурсы и оставляете состояние ресурсов украденного объекта в состоянии, обеспечивающем безопасное уничтожение объекта при вызове его деструктора. Вы не можете ни увидеть, ни получить доступ к временному «значению», из которого вы крадете ресурсы, кроме как в конструкторе/назначении перемещения.
В качестве примера возьмем строку. Вот где вы бы украли выделенные ресурсы и размер из временного объекта и установили бы его значения в свои собственные значения (которые должны быть нулевыми и 0, если ваш объект был создан по умолчанию).