У чому сенс g ++ -порядку?


150

Параметр g ++ -Wall включає -порядок. Що робить цей варіант, описано нижче. Мені не очевидно, чому хтось би переймався (особливо достатньо, щоб це було включено за замовчуванням у -Wall).

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

    struct A {
      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++) { }

80
Це справді має бути прикладом у документації.
Ben S

3
Дякую. Оскільки більшість наших типів є типами POD з простими ініціалізаторами, мені це не траплялося. Ваш приклад набагато кращий, ніж приклад вручну g ++.
Peeter Joot

5
@Mkeke це тому, що ваш компілятор (gcc) ініціалізує неініціалізовані змінні до 0, але це не те, від чого ви повинні залежати; у мене 0 є лише побічним ефектом невідомого значення для неініціалізованих змінних - 0.
ethanwu10

2
@Yakk Сторінка замовлення - людина, відповідь. Ось архів сторінки чоловіка з 2007 року, яка чітко перераховує цей приклад. Отриманий коментар від Бена С - це веселий приклад того, що хтось припускає, що щось існує, навіть не перевіряючи, що це вже є. web.archive.org/web/20070712184121/http://linux.die.net/man/1/…
KymikoLoco

3
@KymikoLoco Це просто неправильно. Приклад на сторінці man - це приклад із ОП (де iініціалізовано 1). Тут iініціалізовано до j, що фактично демонструє проблему.
джазпі

42

Проблема полягає в тому, що хтось може побачити список ініціалізаторів членів у конструкторі і подумати, що вони виконані в такому порядку (j спочатку, потім i). Вони не є, вони виконуються в порядку, визначеному членами в класі.

Припустимо, ви написали A(): j(0), i(j) {}. Хтось може прочитати це і подумати, що я закінчується значенням 0. Це не так, тому що ви ініціалізували його з j, яке містить непотріб, тому що він сам не був ініціалізований.

Попередження нагадує вам написати A(): i(j), j(0) {}, що, сподіваємось, виглядає набагато рибніше.


Виглядає / пахне по-справжньому рибним! :) Однозначно кодовий запах :) Дякую за ваше чітке пояснення, що саме до речі. :)
Will

1
"... нагадує вам написати A (): i (j), j (0) {} ..." Я пропоную вам нагадати про впорядкування членів класу в цьому конкретному випадку.
2.718,

18

Інші відповіді надають кілька хороших прикладів, які виправдовують варіант попередження. Я думав, що надаю історичний контекст. Творець C ++, Bjarne Stroustrup, пояснює у своїй книзі Мова програмування C ++ (3-е видання, Сторінка 259):

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


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, і тому код не буде працювати, як очікувалося. Звідси попередження.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.