Что происходит, когда мы объединяем RAII и GOTO?
Мне интересно, без какой-либо иной цели, кроме чистого любопытства (потому что никому не следовало писать такой код!) о том, как поведение сечений RAII с использованием goto (прекрасная идея не так).
class Two
{
public:
~Two()
{
printf("2,");
}
};
class Ghost
{
public:
~Ghost()
{
printf(" BOO! ");
}
};
void foo()
{
{
Two t;
printf("1,");
goto JUMP;
}
Ghost g;
JUMP:
printf("3");
}
int main()
{
foo();
}
При запуске следующего кода в Visual Studio 2005 я получаю следующий вывод.
1,2,3 BOO!
Однако я воображал, догадывался, надеялся, что "BOO!" на самом деле не появлялось бы, поскольку Ghost никогда не должен был быть создан (IMHO, потому что я не знаю фактического ожидаемого поведения этого кода).
Что?
Я просто понял, что если я создам явный конструктор для Ghost, код не компилируется...
class Ghost
{
public:
Ghost()
{
printf(" HAHAHA! ");
}
~Ghost()
{
printf(" BOO! ");
}
};
А, тайна...
Ответы
Ответ 1
Стандарт говорит об этом явно - с примером; 6.7/3 "Заявление о декларации" (выделено мной мной):
Переменные с автоматической продолжительностью хранения инициализируются каждый раз, когда выполняется их выражение-выражение. Переменные с автоматическим временем хранения, объявленным в блоке, уничтожаются при выходе из блока.
Можно передать в блок, но не таким образом, чтобы обходить объявления с инициализацией. Программа, которая перескакивает с точки, где локальная переменная с продолжительностью автоматического хранения не находится в области до точки, где она находится в области, плохо сформирована, если только переменная не имеет тип POD и объявлена без инициализатора.
[Пример:
void f()
{
//...
goto lx; //ill-formed: jump into scope of a
//...
ly:
X a = 1;
//...
lx:
goto ly; //OK, jump implies destructor
//call for a, followed by construction
//again immediately following label ly
}
-end пример]
Итак, мне кажется, что поведение MSVC не соответствует стандартам - Ghost
не является типом POD, поэтому компилятор должен выдать ошибку, когда инструкция goto
закодирована, чтобы перескочить через нее.
Несколько других компиляторов, которые я пробовал (GCC и Digital Mars), вызывают ошибки. Comeau выдает предупреждение (но, честно говоря, моя сборка script для Comeau настроена для обеспечения высокой совместимости с MSVC, поэтому она может быть преднамеренной для Microsoft).
Ответ 2
Goto не является радиоактивным. Выход из goto немного отличается от ухода за исключением. Ввод по goto должен быть продиктован удобством, а не пределами языка. Не зная, построен ли призрак или нет, это хорошая причина не делать этого.
Перейдите в конструктор. Если вы хотите вступить в игру после того, как какой-либо объект уже сконструирован, заключите его в новую область или иным образом решите свою жизнь самостоятельно.
Ответ 3
В этом случае я нашел следующий подход полезным.
void foo()
{
{
Two t;
printf("1,");
goto JUMP;
}
{
Ghost g;
// operations that use g.
}
// g is out of scope, so following JUMP is allowed.
JUMP:
printf("3");
}
Ограничение области переменной g в вашей функции foo() сделает переход goto законным. Теперь мы не прыгаем с места, где g не инициализируется в место, где ожидается, что g будет инициализироваться.