Какой смысл g ++ -Worder?

150

Опция g ++ -Wall включает в себя -Wreorder. Что эта опция делает, описано ниже. Для меня не очевидно, почему кого-то это волнует (особенно достаточно, чтобы включить это по умолчанию в -Wall).

-Заказ (только C ++)
  Предупреждать, когда порядок инициализаторов членов, указанный в коде, не
  соответствовать порядку, в котором они должны быть выполнены. Например:

    структура А {
      int i;
      int j;
      A (): j (0), i (1) {}
    };

  Компилятор переставит инициализаторы членов для i и j в
  соответствовать порядку декларации членов, выдавая предупреждение
  эффект. Это предупреждение включено -Wall.
Питер Джут
источник
2
Здесь есть несколько хороших ответов, но вкратце, если это кому-то интересно: у g ++ есть флаг, который рассматривает это как полномасштабную ошибку:-Werror=reorder
Макс Барракло

Ответы:

257

Рассматривать:

struct A {
    int i;
    int j;
    A() : j(0), i(j) { }
};

Теперь iинициализируется неким нулевым значением.

Альтернативно, инициализация iможет иметь некоторые побочные эффекты, для которых важен порядок. Например

A(int n) : j(n++), i(n++) { }
int3
источник
80
Это действительно должно быть примером в документации.
Бен С
3
Спасибо. С большинством наших типов, являющихся типами POD с простыми инициализаторами, мне не пришло в голову. Ваш пример намного лучше, чем пример руководства g ++.
Питер Джут
5
@ Майк, потому что ваш компилятор (gcc) инициализирует неинициализированные переменные равными 0, но это не то, от чего вам следует зависеть; Значение 0, являющееся лишь побочным эффектом неизвестного значения для неинициализированных переменных, равно 0.
ethanwu10
2
@Yakk Порядок был man page-> ТАК ответ. Вот архив справочной страницы за 2007 год, в которой приведен этот пример в явном виде. Комментарий Бена С. с приподнятым голосом - это веселый пример того, как кто-то предположил, что что-то существует, даже не проверяя, что оно уже существует. web.archive.org/web/20070712184121/http://linux.die.net/man/1/…
KymikoLoco
3
@KymikoLoco Это просто неправильно. Примером на странице руководства является пример из OP (где iинициализируется до 1). Здесь iинициализируется до j, что фактически демонстрирует проблему.
Jazzpi
42

Проблема в том, что кто-то может увидеть список инициализаторов членов в конструкторе и подумать, что они выполняются в таком порядке (сначала j, затем i). Это не так, они выполняются в том порядке, в котором члены определены в классе.

Предположим, вы написали A(): j(0), i(j) {}. Кто-то может прочесть это и подумать, что я заканчиваю со значением 0. Это не так, потому что вы инициализировали его с помощью j, который содержит мусор, потому что он сам не был инициализирован.

Предупреждение напоминает вам написать A(): i(j), j(0) {}, что, надеюсь, выглядит более подозрительно.

Стив Джессоп
источник
Выглядит / пахнет действительно! :) Определенно запах кода :) Спасибо за ваше четкое объяснение, что прямо к делу. :)
Будет
1
«... напоминает вам написать A (): i (j), j (0) {} ...» Я полагаю, что это напоминает вам переупорядочить членов класса в данном конкретном случае.
2.718
18

Другие ответы предоставили несколько хороших примеров, которые оправдывают возможность предупреждения. Я думал, что предоставлю некоторый исторический контекст. Создатель C ++ Бьярн Страуструп объясняет в своей книге « Язык программирования C ++» (3-е издание, стр. 259):

Конструкторы членов вызываются перед выполнением тела собственного конструктора содержащего класса. Конструкторы вызываются в том порядке, в котором они объявлены в классе, а не в том порядке, в котором они появляются в списке инициализатора. Чтобы избежать путаницы, лучше указывать инициализаторы в порядке объявления. Деструкторы-члены вызываются в обратном порядке построения.

gkb0986
источник
10

Это может укусить вас, если ваши инициализаторы имеют побочные эффекты. Рассматривать:

int foo() {
    puts("foo");
    return 1;
}

int bar() {
    puts("bar");
    return 2;
}

struct baz {
    int x, y;
    baz() : y(foo()), x(bar()) {}
};

Выше будет напечатано «bar», а затем «foo», хотя интуитивно можно предположить, что порядок такой же, как и в списке инициализатора.

В качестве альтернативы, если xи yимеют некоторый пользовательский тип с конструктором, этот конструктор также может иметь побочные эффекты с тем же неочевидным результатом.

Это также может проявиться, когда инициализатор для одного члена ссылается на другого члена.

Павел Минаев
источник
7

Предупреждение существует, потому что если вы просто читаете конструктор, похоже, что jон инициализируется раньше i. Это становится проблемой, если один используется для инициализации другого, как в

struct A {
  int i;
  int j;
  A(): j (0), i (this->j) { }
};

Когда вы просто смотрите на конструктор, это выглядит безопасно. Но на самом деле, он jеще не был инициализирован в точке, где он используется для инициализации i, и поэтому код не будет работать должным образом. Отсюда и предупреждение.

jalf
источник