Почему «виртуальное» наследование не является поведением по умолчанию?

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

Но мой вопрос в том, почему это не поведение по умолчанию в С++ при получении классов, независимо от того, может ли быть проблема с бриллиантом или не может быть?

Есть ли какой-либо «вред» от использования «виртуального» ключевого слова в ситуации, когда наследование бриллианта не представлено?


person Doonyx    schedule 28.02.2014    source источник
comment
Вред заключается в использовании ключевого слова virtual, где оно создает алмазное наследование, но это нежелательно. Рассмотрим банковский счет, который является DollarPaymentEndpoint и YenPaymentEndpoint, где каждый из них наследуется от PaymentEndpoint, который имеет элемент AmountReceived.   -  person David Schwartz    schedule 28.02.2014
comment
Зачем нужно виртуальное ключевое слово? обсуждается, почему virtual не выполняется автоматически.   -  person Raymond Chen    schedule 28.02.2014
comment
@DavidSchwartz Моя ошибка. Наличие виртуального наследования по умолчанию создает неожиданное совместное использование и нарушает инкапсуляцию. это также предотвращает превращение классов в POD.   -  person Raymond Chen    schedule 28.02.2014
comment
@RaymondChen: хотя ответ оказывается очень похожим, я думаю, что другой вопрос предназначен для виртуальных функций-членов, тогда как этот вопрос предназначен для виртуального наследования.   -  person Steve Jessop    schedule 28.02.2014
comment
Виртуальное наследование не избегает алмазов, оно создает алмазы.   -  person Kerrek SB    schedule 28.02.2014
comment
@KerrekSB: Совершенно верно, хотя я думаю, что этот вопрос предназначен для анализа, чтобы избежать проблем двусмысленности (связанных с наследованием алмазов).   -  person Mike Seymour    schedule 28.02.2014
comment
Может ли ответ заключаться просто в том, что грамматика потребует другого ключевого слова для указания, когда виртуальное наследование НЕ требуется? Возможно, повторно используйте explicit, но я не думаю, что это ключевое слово использовалось, когда была разработана грамматика C++.   -  person franji1    schedule 28.02.2014
comment
На этот вопрос уже есть ответ здесь Нет, нет   -  person curiousguy    schedule 16.07.2018
comment
@ franji1 explicit был добавлен для одного конструктора аргументов, а неявное преобразование по умолчанию является ошибкой. Что заставляет вас думать, что не виртуальный дефолт - это ошибка?   -  person curiousguy    schedule 16.07.2018


Ответы (2)


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

Поэтому вам это нужно только в том случае, если вы конкретно хотите ромбовидную структуру; было бы неудобно не забывать указывать невиртуальное наследование, чтобы избежать скрытых накладных расходов. C++ обычно следует принципу, что вы не должны платить за функции, которые вам не нужны.

person Mike Seymour    schedule 28.02.2014

Есть накладные расходы, попробуйте:

#include <iostream>

struct Foo {
    int a;
};

struct Bar : Foo {
    int b;
};

struct Baz : virtual Foo {
    int b;
};

int main() {
    std::cout << sizeof(Foo) << " ";
    std::cout << sizeof(Bar) << " ";
    std::cout << sizeof(Baz) << "\n";
}

В моей реализации я получаю 4 8 16. Для виртуального наследования требуется vptr или аналогичный механизм, поскольку класс Baz не знает, по какому смещению появится подобъект базового класса Foo относительно подобъекта базового класса Baz. Это зависит от того, наследует ли наиболее производный тип также Foo по другому маршруту.

Поскольку есть vptr, можно также ожидать, что при определенных обстоятельствах он будет использоваться, что требует дополнительных затрат :-) То есть требуется одно или несколько дополнительных косвенных обращений для доступа к Foo::a через Baz* или Baz&. Компилятор может избежать этого, если он каким-то образом знает наиболее производный тип реферанда.

person Steve Jessop    schedule 28.02.2014