Списки инициализации С++ - я не понимаю
В Effective С++ сказано, что элементы данных в списке инициализации должны быть перечислены в порядке их объявления. Далее сказано, что аргументация в этом состоит в том, что деструкторы для элементов данных вызываются в обратном порядке их конструкторов.
Но я просто не вижу, как это может быть проблемой...
Ответы
Ответ 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
Далее говорится, что рассуждения для этого деструкторы данных элементы вызываются в обратном порядке порядок их конструкторов.
См. комментарий Стива Джессопа в Порядок инициализации класса компонентов