Инициализация 'unused' пропускается 'goto label' - почему я получаю его для std::string, а не для int?

Я столкнулся с этой ошибкой в ​​некотором коде, и после некоторых экспериментов я наткнулся на эту странность - я получаю ее за std::string, но не для int,

За std::string я получил error C2362: initialization of 'unused' is skipped by 'goto label':

{   goto label;
    std::string unused;
label:;
}

За int Я не получаю никакой ошибки, однако:

{   goto label;
    int unused = 10;
label:;
}

Почему разница? Это потому что std::string есть нетривиальный деструктор?

2 ответа

Решение

Это описано в разделе проекта стандарта C++. 6.7 Заявление декларации, которое говорит (выделение мое):

Можно передавать в блок, но не так, чтобы обойти объявления с инициализацией. Программа, которая переходит на87 из точки, в которой переменная с автоматическим хранением находится вне области действия, до точки, где она находится в области видимости, имеет неправильную форму, если переменная не имеет скалярного типа, типа класса с тривиальным конструктором по умолчанию и тривиальным деструктором, cv-квалифицированная версия одного из этих типов или массив одного из предыдущих типов и объявляется без инициализатора (8.5).

и предоставляет следующий пример:

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
}

Хотя в обоих случаях должна появиться ошибка, поскольку в обоих случаях вы пропускаете инициализацию, это, однако, было бы хорошо:

goto label;
      int unused ;
label:

Так Visual Studio здесь не правильно, оба gcc а также clang генерировать и ошибка для этого кода, gcc говорит:

error:   crosses initialization of 'int unused'
       int unused = 10;
           ^

Конечно Visual Studio может иметь такое расширение, если оно документирует его, но оно не является переносимым для использования такого расширения, как я указал clang а также gcc сгенерировать ошибку для этого.

Мы можем найти обоснование того, почему мы не хотим перепрыгивать через инициализацию в отчете о дефектах 467, в котором стремились добавить такое же ограничение для локальной статической переменной (оно было отклонено):

[...] автоматические переменные, если они не инициализированы явно, могут иметь неопределенные ("мусорные") значения, включая представления ловушек, [...]

Ошибка компилятора. Оба незаконны. Что не является незаконным, однако:

{
    goto label;
    int unused;
    unused = 10;
label:
    ;
}

И то и другое std::string unused; а также int unused = 10; иметь инициализаторы (конструктор по умолчанию в случаеstd::string), и вам нельзя прыгать по определению с помощью инициализатора. Прыжки вокруг одного без инициализатора, вероятно, позволят избежать взлома кода вроде

switch ( something )
{
    int i;
case 0:
    i = x;
    // ...
    break;

case 1:
    i = y;
    //  ...
    break;
//  ...
}

Я бы не назвал этот хороший код, но я бы не удивился, обнаружив его в более старом C, а C++ старается не ломать подобные вещи.

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