Безопасно ли присвоение перемещения с помощью destruct + move construct?

Вот очень простой способ определить назначение перемещения для большинства классов с помощью конструктора перемещения:

class Foo {
public:
  Foo(Foo&& foo);                     // you still have to write this one
  Foo& operator=(Foo&& foo) {
    if (this != &foo) {               // avoid destructing the only copy
      this->~Foo();                   // call your own destructor
      new (this) Foo(std::move(foo)); // call move constructor via placement new
    }
    return *this;
  }
  // ...
};

Безопасна ли эта последовательность вызова вашего собственного деструктора с последующим размещением new на указателе this в стандартном C ++ 11?


person Bjarke H. Roune    schedule 26.10.2012    source источник
comment
Ваш конструктор ходов должен быть noexcept, иначе вы попытаетесь уничтожить уже разрушенный объект, если он бросится и уйдет в UB-land.   -  person ildjarn    schedule 26.10.2012
comment
Хороший трюк для назначения перемещения / копирования - просто взять параметр по значению. Пользователь либо переместит-построит параметр значения, либо скопирует-построит его (или пропустит его). Затем вы можете использовать std::swap, чтобы поменять значение на свой объект.   -  person Nicol Bolas    schedule 27.10.2012


Ответы (2)


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

person Pete Becker    schedule 26.10.2012
comment
Есть ли также проблема с взятием новой выделенной памяти, ее ручным хранением и последующим ее размещением? Мне пришлось бы вникнуть в стандарт, чтобы проверить, разрешено ли это самому. - person Yakk - Adam Nevraumont; 26.10.2012
comment
@Yakk нет, раз вы ничего не [де] распределяете. - person Seth Carnegie; 26.10.2012
comment
@Yakk нет, в этом отношении не должно быть никаких проблем, если объект, который вы изначально выделили, имеет размер и требования к выравниванию, достаточные для объекта, который вы создаете с помощью нового размещения, а указатель, который вы передаете для удаления, имеет соответствующий тип. - person bames53; 26.10.2012
comment
Если производный класс имеет виртуальный деструктор или ему нужно сделать что-то особенное для назначения перемещения, не должно ли назначение перемещения быть виртуальным, а также переопределено в подклассе? В этом случае производный класс может таким же образом реализовать назначение перемещения. Если автор производного класса не знает, что нельзя вызывать назначение перемещения суперкласса, тогда возникает проблема, да - это либо полностью, либо нет. Это конкретно вас беспокоит? - person Bjarke H. Roune; 26.10.2012
comment
Также мне кажется, что классам с подтипами часто будет трудно поддерживать назначение перемещения в суперклассе. Если у вас есть два подтипа Bar и Baz, и вы пытаетесь переместить Bar в Baz с помощью назначения перемещения Foo, тогда все будет сложно независимо от этой техники. Я не уверен, что заимствование и перемещение действительно хорошо сочетаются в целом? - person Bjarke H. Roune; 26.10.2012
comment
@ BjarkeH.Roune - да, если производные типы не предполагают, что объекты работают обычным образом, возможно, можно будет получить что-то, что более или менее взаимосвязано. Но зачем идти на такие неприятности? Написать оператор присваивания перемещения не так уж и сложно, и такой подход к его устранению ставит ловушки для специалистов по сопровождению и пользователей. - person Pete Becker; 26.10.2012
comment
class Bar: Foo {int x; }; Foo * f = новый бар (); * f = Foo (); delete f; - мы выделяем Bar, уничтожаем его как ~ Foo, размещаем на его месте Foo, затем удаляем его как Bar. Я был бы удивлен, если бы это не было неопределенным поведением. - person Yakk - Adam Nevraumont; 26.10.2012
comment
@Yakk: Я подозреваю, что на самом деле второе удаление вызовет деструктор Foo - если программа зайдет так далеко. Однако первое удаление определенно неверно. - person dhardy; 05.02.2013
comment
@PeteBecker или кто-нибудь еще, не могли бы вы уточнить, почему наследование от этого класса превратит объект в чудовище? Я понимаю, что можно столкнуться с проблемами нарезки, но это не из-за использования этой техники как таковой. Или я что-то упустил? - person Innocent Bystander; 15.09.2018
comment
@InnocentBystander - добавить виртуальную функцию и переопределить ее в производном классе. Теперь (здесь слишком много деталей реализации) вызов конструктора в этом операторе присваивания заменит указатель vtable производного типа на указатель vtable базового типа. Вуаля, объект производного типа, но виртуальные вызовы не передаются своим переопределителям. - person Pete Becker; 15.09.2018
comment
@PeteBecker хм, я вижу, что ты там делал. Итак, чтобы это работало, каждый производный класс должен (а) вызвать свой dtor, (б) вызвать родительский класс 'move oper =' и (в) вызвать его размещение как новое. Да, это чудовищно, правда. - person Innocent Bystander; 15.09.2018

Технически исходный код безопасен в этом крошечном примере. Но реальность такова, что если вы хоть раз посмотрите на Foo смешно, вы вызовете UB. Это настолько ужасно опасно, что оно того не стоит. Просто используйте своп, как и все остальные - для этого есть причина, и это потому, что это правильный выбор. К тому же самопроверка - это плохо.

person Puppy    schedule 26.10.2012
comment
Копирование и обмен - не всегда лучшая реализация. Это один из простых способов получить надежную гарантию безопасности исключений, но есть компромиссы. Говард Хиннант частично обсуждает это в этом ответе. - person bames53; 26.10.2012
comment
Вас беспокоит в основном автор производного класса (в данном случае ошибочно), вызывающий назначение перемещения Foo, или есть еще проблемы, чем это? Этот метод требует от авторов подкласса виртуального переопределения и использования этого метода. Большинство проблем, которые я могу придумать помимо этого, связаны с тем, что назначение хода не является виртуальным, и тогда ответ просто заключается в том, что назначение хода должно быть виртуальным в тех случаях, независимо от того, используется ли этот метод (вы согласны?). Есть ли у вас проблемы помимо этого? - person Bjarke H. Roune; 26.10.2012