Каков порядок инициализации членов класса?

Я только что прочитал в этот ответ, что если у вас есть следующий код

class Base
{
    public Base()
    {

    }
}

class One : Base
{
    string test = "text";
}

class Two : Base
{
    string test;
    public Two()
    {
        test = "text";
    }
}

Для класса One инициализация One.test будет инициализирована до вызова Base::Base. Но Two.test будет инициализирован после вызова Base::Base.

Я предполагаю, что это потому, что в обоих случаях это

  1. поля ‹- включая One.test
  2. База::База()
  3. One::One() или Two::Two() ‹- который инициализирует Two.test

Кажется, я помню, что списки инициализации находятся непосредственно перед конструктором. Итак, порядок инициализации:

  1. поля
  2. базовый список инициализаторов
  3. базовый конструктор
  4. собственный список инициализаторов
  5. собственный конструктор

И при чем тут поля Базы? Все ли поля инициализируются первыми при выделении памяти или непосредственно перед списком инициализации текущего базового класса?

И есть ли другие шаги в списке, которые вы можете придумать?

Я был бы признателен, если бы кто-нибудь мог дать хороший обзор.


person Sarien    schedule 25.10.2013    source источник
comment
Итак, как вы связываете ответ C# с C++?   -  person 0x499602D2    schedule 25.10.2013
comment
Совсем пропустил это. Ну, я все же хотел бы знать, как это работает в C++.   -  person Sarien    schedule 25.10.2013
comment
Написанный код недействителен в C++.   -  person masoud    schedule 25.10.2013
comment
Много ошибок в коде. Стоит сначала скомпилировать его самостоятельно, а затем вырезать и вставить код в SO.   -  person Martin York    schedule 26.10.2013


Ответы (3)


Инициализация C++ происходит в следующем порядке:

  1. Базовые классы, слева направо
  2. Переменные-члены в том порядке, в котором они объявлены

Инициализация базовых классов из шага 1 рекурсивно выполняет те же шаги. Таким образом, все базы полностью создаются до того, как произойдет инициализация любой переменной-члена и до того, как тело конструктора начнет выполнение.

Поэтому, когда компилятор встречает:

Two two;

Сначала Two::Two начинает выполнение, начиная со списка инициализаторов. Все базы инициализируются через список инициализации, даже если вы не записали его или пропустили инициализацию базового класса. Итак, код, который на самом деле работает, выглядит примерно так:

Two::Two
:
  One(),
  test()
{
  test = "text";
}

Список инициализаторов выполняется перед телом конструктора. Таким образом, One полностью создается до того, как тело Two::Two начнет выполняться.

В свою очередь, One выглядит так:

One::One()
: 
  Base()
{
  string test = "test";
}

А Base пусто:

Base::Base()
{
}

Итак, что происходит, когда выполняется Two two;:

  1. Base построен.
  2. One построен
  3. Автоматическая переменная test создается, инициализируется и уничтожается в контексте One::One.
  4. Two::test инициализируется по умолчанию
  5. Two::test присваивается значение "текст"

Обратите внимание, что некоторые из них, особенно шаги 4 и 5, могут быть оптимизированы компилятором, если он сочтет это безопасным.

person John Dibling    schedule 25.10.2013
comment
@LokiAstari: О, упс. Я думал, что это выглядит так: class Two : public One и class One : public Base - person John Dibling; 26.10.2013
comment
Случай One не совсем такой, как изображен в этом ответе. Нет локальной переменной test, а есть переменная-член со значением инициализатора. Это преобразуется компилятором в: One() : Base(), test("test") {} - person David Rodríguez - dribeas; 26.10.2013

Для класса One инициализация One.test будет инициализирована до вызова Base::Base. Но Two.test будет инициализирован после вызова Base::Base.

Нет. Базы инициализируются перед любым элементом.

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

Виртуальные базы инициализируются перед любой другой базой, в другой, которая определяется поиском в глубину, идущим от первой к последней объявленной базе.

В случае с Two есть одна деталь, которая, вероятно, имеет значение, и я не уверен, что вы знаете, элемент test инициализируется в списке инициализаторов Two, до входа в тело конструктора, а затем присваивается.

person David Rodríguez - dribeas    schedule 25.10.2013
comment
Инициализация базы включает в себя рекурсивные базы, список инициализации, элементы и конструктор, верно? - person Sarien; 26.10.2013
comment
@Sarien: правильная, полная инициализация, включая базы, члены и тело конструктора. - person David Rodríguez - dribeas; 26.10.2013

Другие ответили на вопрос.
Но следующая демонстрация может быть полезной.

#include <iostream>
class String
{
    public:
        String(char const* d)               {std::cout << "String Constructor: " << d << "\n";}
        String()                            {std::cout << "String Constructor: Default\n";}
        String(String const& rhs)           {std::cout << "String Constructor: Copy\n";}
        String& operator=(String const& rhs){std::cout << "String Assignment\n";}
        ~String()                           {std::cout << "String Destructor\n";}
};

class Base
{
    public: Base()
    {
        std::cout << "Base::Base()\n";
    }
};

class One : Base
{
    String test = "text";
};

class Two : Base
{
    String test;
    public: Two()
    {
        std::cout << "Two::Two\n";
        test = "text";
    }
};

int main()
{
    std::cout << "Trying One\n";
    One     one;

    std::cout << "==========\n\n\n";
    std::cout << "Trying Two\n";
    Two     two;

    std::cout << "==========\n\n\n";
    std::cout << "Trying Base\n";
    Base    b;
}

Результат этого:

> ./a.out
Trying One                    // Outside the class about to start
Base::Base()                  // One: Calls the base constructor first Base
String Constructor: text      // One: Constructs its members.
==========


Trying Two                    // Outside the class about to start
Base::Base()                  // Two: Calls the base construtor first
String Constructor: Default   // Two: Constructs its members next
Two::Two                      // Two: Now entering the body of the constructor
String Constructor: text      //      Builds a string
String Assignment             //      Calls the assignment constructor.
String Destructor             //      Temporary destroyed.
==========                    //


Trying Base
Base::Base()
String Destructor             // Destroys the string in Two
String Destructor             // Destroys the string in One
person Martin York    schedule 25.10.2013