Давайте перечислим операции, которые мы должны рассмотреть:
Строительство / уничтожение
Каждый конструктор / деструктор будет вызывать свои эквиваленты базового класса. Однако, как отметил Джеймс МакНеллис, вы, очевидно, все равно собираетесь делать эту работу. Вы не получили от А только потому, что он был там. Так что работа будет выполнена так или иначе.
Да, это потребует еще нескольких вызовов функций. Но накладные расходы на вызовы функций будут ничем по сравнению с реальной работой, которую фактически должна будет выполнять любая существенно глубокая иерархия классов. Если вы находитесь в точке, где накладные расходы на вызов функций действительно важны для производительности, я настоятельно рекомендую вообще не вызывать конструкторы в этом коде.
Размер объекта
Как правило, накладные расходы для производного класса - ничто. Накладные расходы для виртуальных членов - это указатель или для виртуального наследования.
Вызовы функций-членов, статические
Под этим я подразумеваю вызов не виртуальных функций-членов или вызов виртуальных функций-членов с именами классов (синтаксис ClassName :: FunctionName). Оба они позволяют компилятору знать во время компиляции, какую функцию вызывать.
Производительность этого не зависит от размера иерархии, поскольку определяется время компиляции.
Вызовы функций-членов, динамические
Это вызов виртуальных функций с полным и полным ожиданием вызовов во время выполнения.
В большинстве нормальных реализаций C ++ это не зависит от размера иерархии объектов. Большинство реализаций используют v-таблицу для каждого класса. Каждый объект имеет указатель v-таблицы в качестве члена. Для любого конкретного динамического вызова компилятор обращается к указателю v-таблицы, выбирает метод и вызывает его. Поскольку v-таблица одинакова для каждого класса, она не будет медленнее для класса с глубокой иерархией, чем для класса с мелкой.
Виртуальное наследование играет немного с этим.
Pointer Casts, Static
Это относится к static_cast
или любой другой эквивалентной операции. Это означает неявное приведение из производного класса к базовому классу, явное использование static_cast
или приведений в стиле C и т. Д.
Обратите внимание, что технически это относится к эталонному литью.
Производительность статических приведений между классами (вверх или вниз) не зависит от размера иерархии. Любые смещения указателя будут сгенерированы во время компиляции. Это должно быть верно как для виртуального, так и для не виртуального наследования, но я не уверен на 100% в этом.
Указатель Приведения, Динамический
Это, очевидно, относится к явному использованию dynamic_cast
. Обычно это используется при приведении базового класса к производному.
Производительность dynamic_cast
, вероятно, изменится для большой иерархии. Но разумные реализации должны проверять только классы между текущим классом и запрошенным. Так что это просто линейно по количеству классов между двумя, а не линейно по количеству классов в иерархии.
TYPEOF
Это означает использование оператора typeof
для извлечения объекта std::type_info
, связанного с объектом.
Производительность этого будет зависеть от размера иерархии. Если класс является виртуальным (имеет виртуальные функции или виртуальные базовые классы), он просто извлечет его из виртуальной таблицы. Если он не виртуальный, то он определен во время компиляции.
Заключение
Короче говоря, большинство операций инвариантны с размером иерархии. Но даже в тех случаях, когда это оказывает влияние, это не проблема.
Меня больше интересовала бы некая этика дизайна, когда вы чувствовали необходимость построения такой иерархии. По моему опыту, подобные иерархии происходят из двух линий дизайна.
Java / C # идеален для того, чтобы все происходило из общего базового класса.Это ужасная идея в C ++ и никогда не должна использоваться.Каждый объект должен происходить от того, что ему нужно , и только от этого.C ++ был построен по принципу «плати за то, что ты используешь», и использование общей базы работает против этого.В общем, все, что вы могли бы сделать с таким общим базовым классом, это то, что вы не должны делать точка, или что-то, что можно сделать с перегрузкой функции (например, используя operator<<
для преобразования в строки).
Злоупотребление наследованием.Использование наследования, когда вы должны использовать сдерживание.Наследование создает отношения «есть» между объектами.Чаще всего отношения «имеет» (один объект имеет другой в качестве члена) гораздо более полезны и гибки.Они упрощают скрытие данных, и вы не позволяете пользователю притворяться, что один класс - это другой.
Убедитесь, что ваш дизайн не противоречит одному из этих принципов.