Подтвердить что ты не робот

Что происходит, когда мы объединяем 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! ");
    }
};

А, тайна...

4b9b3361

Ответ 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 будет инициализироваться.