Уничтожить, а затем построить новый объект, используя ту же переменную

Иногда приятно начать все сначала. В C++ я могу использовать следующий простой маневр:

{

    T x(31, Blue, false);

    x.~T();                        // enough with the old x

    ::new (&x) T(22, Brown, true); // in with the new!

    // ...
}

По окончании действия деструктор снова запустится и все будет хорошо. (Давайте также скажем, T немного особенный и не любит, когда его назначают, не говоря уже об обмене. Есть ли возможный улов с этим подходом?

6 ответов

Решение

Я думаю, что единственный способ сделать это действительно безопасным - это вызвать вызываемый конструктор noexceptНапример, добавив static_assert:

static_assert(noexcept(T(22, Brown, true)), "The constructor must be noexcept for inplace reconstruction");
T x(31, Blue, false);
x.~T();
::new (&x) T(22, Brown, true);

Конечно, это будет работать только для C++11.

Если Tконструктор бросает на вторую конструкцию, у вас проблема. Если вам нравятся переборы, проверьте это:

T x(31, Blue, false);
x.~T();
const volatile bool _ = true;
for(;_;){
  try{
    ::new (&x) T(22, Brown, true);
    break; // finally!
  }catch(...){
    continue; // until it works, dammit!
  }
}

Это даже обеспечивает сильную гарантию исключения!


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

И здесь на самом деле есть способ обойти неопределенное поведение двойного уничтожения:

#include <cstdlib>

T x(31, Blue, false);
x.~T();
try{
  ::new (&x) T(22, Brown, true);
}catch(...){
  std::exit(1); // doesn't call destructors of automatic objects
}

Если строительное выражение T выбрасывает, вы дважды уничтожите объект, который является UB. Конечно, даже желание сделать это свидетельствует о неудаче проекта.

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

@1 sub nerve.cells, fa0h
@2 xor x, x     // bitch.
@3 mov out, x
@4 test out, out
@5 jne @1
@6 xor x, x     // just in case.
@7 sub money, 2BC   // dammit.
@8 mov %x, new.one
@8 cmp new.one, %x 
@9 jne @7   
...
@25 jmp @1      // sigh... 

Ммм. Поскольку вы делаете все, что не одобряет C++, я думаю, что все забывают о goto.

Обратите внимание, что после явного X.~T() вызов, и до того, как он будет восстановлен1, все равно будет двойное уничтожение, если кто-то сделал goto до объявления / инициализации переменной x (даже в пределах внутреннего блока области видимости).

Поскольку вы, очевидно, могли бы просто задокументировать это, я не буду пытаться "исправить" это. Концептуально вы могли бы разработать класс RAII для управления восстановлением объекта на месте, что делает этот маневр безопасным для перемещения в любом месте. Обратите внимание, что вы можете получить вызов конструктора Placement New, который будет перенаправлен из деструктора объекта менеджера RAII. Жизнь хороша.

Другие предостережения все еще применяются, конечно (см. Другие ответы)


1 мы можем предположить, что на этот момент мы построим

Ничто не мешает вам делать это, в большинстве случаев это сработает. Но, как и во многих C++, знание специфики ваших кейсов будет разницей между тем, как он работает, как вы хотите, и дампом ядра. Есть очень немного примеров причин, по которым я мог понять, почему вы захотите сделать это в реальной программе, единственное, что имеет смысл - это файл с отображением в памяти.

Другие вопросы по тегам