Завершить виртуальное наследование

В моем коде есть базовый ромбовидный узор:

     CommonBase
        /  \
       /    \
 DerivedA  DerivedB
       \    /
        \  /
       Joined

Он реализован следующим образом: общий базовый класс имеет конструктор по умолчанию, а конструктор принимает параметр:

struct CommonBase {
    CommonBase() : CommonBase(0) {}

    CommonBase(int val) : value(val) {}

    const int value;
};

struct DerivedA : public virtual CommonBase {
    void printValue() {
        std::cout << "The value is " << value << "\n";
    }
};

struct DerivedB : public virtual CommonBase {
    void printValueTimes2() {
        std::cout << "value * 2 is " << value * 2 << "\n";
    }
};

struct Joined : public DerivedA,
                public DerivedB {
    Joined(int val) : CommonBase(val) {
        std::cout << "Constructor value is " << val << "\n";
        std::cout << "Actual value is " << value << "\n";
    }
};

Класс Joined инициализирует виртуальную базу с помощью конструктора, который принимает параметр, и все работает, как ожидалось.

Однако, когда я получаю класс из класса Joined, происходит что-то странное - конструктор по умолчанию называетсяCommonBase, если я также явно не инициализирую CommonBase в конструкторе производных классов.

Это демонстрируется с помощью этого кода:

struct JoinedDerivedA : public Joined {
    JoinedDerivedA() : Joined(99) {
        printValue();
    }
};

struct JoinedDerivedB : public Joined {
    JoinedDerivedB() : Joined(99), CommonBase(99) {
        printValue();
    }
};

int main() {
    std::cout << "======= Joined =======\n";
    Joined j(99);
    j.printValue();
    std::cout << "\n=== JoinedDerivedA ===\n";
    JoinedDerivedA a;
    std::cout << "\n=== JoinedDerivedB ===\n";
    JoinedDerivedB b;

    return 0;
}

Результатом этого кода является

======= Joined =======
Constructor value is 99
Actual value is 99
The value is 99

=== JoinedDerivedA ===
Constructor value is 99
Actual value is 0 // <-- unexpected behaviour
The value is 0

=== JoinedDerivedB ===
Constructor value is 99
Actual value is 99
The value is 99

Почему это так? Можно ли снова не инициализировать общий базовый класс в производных классах?

Вот код на ideone, так что вы можете запустить его самостоятельно: https://ideone.com/Ie94kb

Когда у вас есть виртуальное наследование, существует только один подобъект общего базового класса для всех, который является производным виртуально от этого базового класса. И этот подобъект находится в самом производном классе. Где еще ему жить? На самом деле нет другого места, где вы всегда могли бы выразить это в четко определенной манере. Вы можете посмотреть stackoverflow.com/questions/6461784/…

Michael Kenzel 17.12.2018 12:54

@MichaelKenzel Я понимаю, что существует только один экземпляр общего базового класса, однако я ожидал, что он будет жить в классе Joined, поскольку этот класс явно вызывает конструктор базового класса, аналогично невиртуальному наследованию.

CrushedPixel 17.12.2018 12:57

А что, если есть другой базовый класс, который также вызывает конструктор виртуального базового класса в своем конструкторе?

Michael Kenzel 17.12.2018 13:06

Я понимаю. Значит, нет возможности сделать это красиво?

CrushedPixel 17.12.2018 13:20

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

CrushedPixel 17.12.2018 13:20

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

r3mus n0x 17.12.2018 13:25
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
7
83
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это указано в Инициализация баз и членов [class.base.init] (12.6.2 в проекте n4567). Мы можем прочитать в § 13 (подчеркните мой):

(13) In a non-delegating constructor, initialization proceeds in the following order:

(13.1) — First, and only for the constructor of the most derived class (1.8), virtual base classes are initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where “left-to-right” is the order of appearance of the base classes in the derived class base-specifier-list.

(13.2) — Then, direct base classes are initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers).

(13.3) — Then, non-static data members are initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).

(13.4) — Finally, the compound-statement of the constructor body is executed.

[ Note: The declaration order is mandated to ensure that base and member subobjects are destroyed in the reverse order of initialization. —end note ]

Это означает, что виртуальный базовый класс будет инициализирован до при инициализации Joined. Таким образом, в DerivedJoinedA он по умолчанию инициализируется с value, равным 0. Затем при инициализации Joined инициализация CommonBase игнорируется, потому что он уже был инициализирован, и value сохраняет свое значение 0.

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

Спасибо за подробное объяснение. Объяснение со ссылкой на стандарт было тем, на что я надеялся!

CrushedPixel 17.12.2018 17:29

Другие вопросы по теме