Что происходит, когда мы объединяем RAII и GOTO? - PullRequest
14 голосов
/ 09 марта 2010

Мне интересно, ни с какой другой целью, кроме чистого любопытства (потому что никто никогда НЕ ДОЛЖЕН писать такой код!) О том, как поведение 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 никогда не нужно было бы создавать (ИМХО, потому что я не знаю фактическое ожидаемое поведение этого кода).

Что случилось?


Я только что понял, что если я создаю явный конструктор для Ghost, код не компилируется ...

class Ghost
{
public:
    Ghost()
    {
        printf(" HAHAHA! ");
    }
    ~Ghost()
    {
        printf(" BOO! ");
    }
};

Ах, тайна ...

Ответы [ 3 ]

24 голосов
/ 09 марта 2010

Стандарт говорит об этом явно - с примером; 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
}

- конец примера]

Так что мне кажется, что поведение MSVC не соответствует стандартам - Ghost не является типом POD, поэтому компилятор должен выдать ошибку, когда оператор goto закодирован, чтобы перепрыгнуть через него.

Пара других компиляторов, которые я пробовал (GCC и Digital Mars), выдают ошибки. Comeau выдает предупреждение (но, честно говоря, мой сценарий сборки для Comeau настроен на высокую совместимость с MSVC, поэтому он может намеренно следовать примеру Microsoft).

0 голосов
/ 05 января 2017

В этом сценарии я нашел следующий подход полезным.

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.

0 голосов
/ 09 марта 2010

Гото не радиоактивен. Уход по goto мало чем отличается от ухода по исключению. Вступление goto должно продиктовано удобством, а не ограничениями языка. Непонимание того, создан ли призрак или нет, является веской причиной не делать этого.

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

...