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

Списки инициализации С++ - я не понимаю

В Effective С++ сказано, что элементы данных в списке инициализации должны быть перечислены в порядке их объявления. Далее сказано, что аргументация в этом состоит в том, что деструкторы для элементов данных вызываются в обратном порядке их конструкторов.

Но я просто не вижу, как это может быть проблемой...

4b9b3361

Ответ 1

Хорошо рассмотрим следующее:

class Class {
    Class( int var ) : var1( var ), var2(var1 ) {} // allright
    //Class( int var ) : var2( var ), var1(var2 ) {} // var1 will be left uninitialized

    int var1;
    int var2;
};

Второй (прокомментированный) конструктор выглядит в порядке, но на самом деле будет инициализирован только var2 - сначала будет инициализирован var1, и он будет инициализирован с помощью var2, который еще не инициализирован в этой точке.

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

Ответ 2

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

Рассмотрим простой пример:

class MyString {
public:
  size_t s_length;
  std::string s;
  MyString(const char *str) : s(str), s_length(s.length()) {}
};

Цель этого примера состоит в том, что член s_length содержит длину сохраненной строки. Однако это не сработает, потому что s_length будет инициализирован до s. Поэтому вы вызываете s.length до выполнения конструктора s!

Ответ 3

Например, если у вас есть такой класс:

class X {
  int a,b;
  X(int i) : b(i),a(b) { } // Constructor
};

Конструктор для класса X выглядит так, что сначала инициализирует "b", но он фактически инициализируется в порядке объявления. Это означает, что он сначала инициализирует "а". Однако "a" инициализируется значением "b", которое еще не было инициализировано, поэтому "a" получит значение нежелательной почты.

Ответ 4

Разрушение - это обратная сторона конструкции, поэтому элементы разрушаются в обратном порядке.

Предположим, что у нас есть 2 члена, a и b. b зависит от a, но a не зависит от b.

Когда мы строим, сначала строим a, и теперь он существует, мы можем построить b. Когда мы разрушаем, если мы разрушаем a, сначала это будет проблемой, поскольку от нее зависит b. Но сначала мы разрушаем b и гарантируем целостность.

Это типично. Например, в теории групп инверсия fg равна ~g~f (где ~f является обратным к f)

Когда вы одеваетесь, вы сначала надеваете носки, а затем надеваете обувь. Когда вы раздеваете, сначала удаляйте обувь, а затем носки.

Ответ 5

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

#include <iostream>

struct A1 {
  A1(int) { std::cout << "A1::A1(int)" << std::endl; }
  ~A1() { std::cout << "A1::~A1()" << std::endl; }
};

struct A2 {
  A2(int) { std::cout << "A2::A2(int)" << std::endl; }
  ~A2() { std::cout << "A2::~A2()" << std::endl; }
};

struct B {
  B(int) { std::cout << "B::B(int)" << std::endl; throw 1; }
  ~B() { std::cout << "B::~B()" << std::endl; }
};

struct C {
  C() : a1(1), a2(2), b(3) { std::cout << "C::C()" << std::endl; } // throw 1; }
  ~C() { std::cout << "C::~C()" << std::endl; }
  A1 a1;
  A2 a2;
  B b;
};

int main() {
  try {
    C c;
  } catch (int i) {
    std::cout << "Exception!\n";
  }
}

Результат будет примерно таким:

A1::A1(int)
A2::A2(int)
B::B(int)
A2::~A2()
A1::~A1()
Exception!

Ответ 6

Далее говорится, что рассуждения для этого деструкторы данных элементы вызываются в обратном порядке порядок их конструкторов.

См. комментарий Стива Джессопа в Порядок инициализации класса компонентов