Измените элемент данных базового класса из конструктора производного класса в C

#c #c 14

Вопрос:

Я не могу понять вывод этого кода. Я ожидаю 10 , что меня напечатают, но результат есть 85 . Может кто-нибудь объяснить, что происходит?

 #include <iostream>
using namespace std;
class A
{  
    public:   
        int x=3;
        A(int a) : x(a) {}
};

class B: public A
{   
    public:  
        int y = 10;
        B() : A(y) {}
};

int main()
{
    B b;    
    cout << b.x << endl;
    return 0;
}
 

Но, изменив его на:

 class B: public A
{   
    public:  
        int y = 10;
        B(int s) : A(s) {}
};

int main()
{
    B b(4); 
    cout << b.x << endl;
    return 0;
}
 

Он работает так, как ожидалось (печать 4).

Комментарии:

1. Порядок инициализации не такой, как вы ожидаете.

2. предупреждения-ваш друг: предупреждение: ‘*<неизвестно>.B::y’ используется неинициализированным в этой функции-Wuninitialized<неизвестно>

3. @user1810087 я не получил никакого предупреждения, когда запускал код на терминале

4. @Someprogrammerdude спасибо за ссылку! Это все четко объясняло.

5. @spab обратите внимание на флаги » — Wall-Wextra -педантичный`. Я думаю, что большинство разработчиков согласятся включить предупреждения как можно более экстремальные и отключать их только в случае необходимости. Для VS это будет /W4.

Ответ №1:

Это называется «неопределенное поведение».

Сначала создаются базовые классы, прежде чем будут созданы члены класса.

В этом случае вы передаете содержимое неинициализированного члена класса конструктору базового класса.

Тот факт, что у вас есть инструкция инициализации для члена класса, которая отображается в предыдущей строке .cpp файла, не означает, что это порядок инициализации. C не всегда работает таким образом. Вы передаете неинициализированный y конструктор базового класса, а затем, когда базовый класс возвращает, создается подкласс, устанавливающий y значение, для которого он инициализирован.

Комментарии:

1. Спасибо за ответ. Но я не уверен, что это неопределенное поведение. Ответ @Someprogrammerdude прояснил это для меня. Я принимаю этот ответ.

2. @spab Я не уверен, что вы имеете в виду — ни один из людей, которые опубликовали ответы, не назван Someprogrammerdude.

3. Вы сами наблюдали неопределенное поведение: бессмысленный результат.

4. @DavidZ 1 — й комментарий к вопросу от Someprogrammerdude

5. @SamVarshavchik конечно. Но документация, направленная каким-то программистом, дала понимание на более высоком уровне. Некоторые части вашего ответа показались мне расплывчатыми/вводящими в заблуждение: 1) «Базовые классы создаются первыми, до создания членов класса» — возможно, вы имели в виду «члены класса инициализируются» 2) «Когда базовый класс возвращает, подкласс создается» — это явно не указывает, когда члены класса инициализируются. Ответ какой-то программы полностью прояснил мой вопрос. Спасибо!

Ответ №2:

Из-за правил порядка инициализации в первом случае вы вызываете конструктор() до того, как y было присвоено значение 10, поэтому значение y не определено и зависит от текущего значения этих байтов sizeof(int) в стеке. Поэтому вы инициализируете x этим неопределенным полуслучайным значением и печатаете его позже. Во втором случае вы вызываете B(int) с s = 4, и он успешно инициализирует x с помощью 4.

Ответ №3:

Как следует из ссылки в комментарии @Someprogrammerdude:

Порядок инициализаторов элементов в списке не имеет значения: фактический порядок инициализации выглядит следующим образом:

  1. Если конструктор предназначен для наиболее производного класса, виртуальные базы инициализируются в том порядке, в котором они отображаются при первом обходе деклараций базового класса слева направо (слева направо относится к появлению в списках базовых спецификаторов).
  2. Затем прямые базы инициализируются в порядке слева направо, как они отображаются в списке базовых спецификаторов этого класса
  3. Затем нестатические элементы данных инициализируются в порядке объявления в определении класса.
  4. Наконец, выполняется тело конструктора