Несколько классов базовых классов замедляют класс/структуру в С++?

Имеет ли несколько уровней базовых классов замедление класса? A получает значения B, выводимые на C, получает производные D, F получает G,...

Несколько наследований замедляет класс?

Ответ 1

Не виртуальные вызовы функций абсолютно не влияют на производительность во время выполнения, в соответствии с мангой С++, что вы не должны платить за то, что вы не используете. В вызове виртуальной функции вы обычно платите за дополнительный поиск указателя, независимо от того, сколько уровней наследования или количества базовых классов у вас есть. Конечно, это вся реализация определена.

Изменить: как отмечено в другом месте, в некоторых сценариях множественного наследования требуется настройка указателя 'this' перед выполнением вызова. Раймонд Чен описывает как это работает для COM-объектов. В принципе, вызов виртуальной функции для объекта, который наследуется от нескольких баз, может потребовать дополнительного вычитания и команды jmp поверх дополнительного поиска указателя, необходимого для виртуального вызова.

Ответ 2

[Иерархии глубоких наследований] значительно увеличивает нагрузку на обслуживание, добавляя излишнюю сложность, заставляя пользователей изучать интерфейсы многих классов, даже когда все, что они хотят сделать, это использовать определенный производный класс. Он также может влиять на использование памяти и производительность программы, добавляя ненужные vtables и косвенные отношения к классам, которые им действительно не нужны. Если вы часто создаете глубокие иерархии наследования, вы должны просмотреть свой стиль дизайна, чтобы убедиться, что вы выбрали эту дурную привычку. Глубокие иерархии редко нужны и почти никогда не хороши. И если вы не верите в это, но думаете, что "OO просто не OO без большого наследования", то хороший контрпример, который следует рассмотреть, [С++] стандартная библиотека. - Herb Sutter

Ответ 3

  • Уменьшается ли множественное наследование класс?

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

Однако, когда задействовано множественное наследование, иногда возникают незначительные дополнительные накладные расходы при вызове виртуальной функции с помощью указателя базового класса. В этом случае некоторые реализации имеют виртуальную функцию, проходящую через небольшой кусок, который настраивает указатель 'this', поскольку

(static_cast<Base*>( this) == this)

Не обязательно true в зависимости от макета объекта.

Обратите внимание, что все это зависит от очень, очень.

См. Lippman "Внутри объектной модели С++" Глава 4.2 - Виртуальные функции-члены/Виртуальные функции в MI

Ответ 4

Существует не разница в скорости между виртуальными вызовами на разных уровнях, так как все они выравниваются в vtable (указывая на наиболее производные версии переопределенных методов). Таким образом, вызов ((A *) inst) → Method(), когда inst является экземпляром B, является одним и тем же накладным капиталом, например, когда inst является экземпляром D.

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

Ответ 5

Виртуальные вызовы более трудоемки, чем обычные вызовы, потому что они должны искать адрес фактической функции для вызова из таблицы vtable

Кроме того, оптимизация компилятора, такая как встраивание, может быть затруднена из-за требования к поиску. Ситуации, в которых невозможно встроить инлайн, могут привести к довольно высоким накладным расходам из-за стека и операций push и jump

Вот правильное исследование, в котором говорится, что накладные расходы могут достигать 50% http://www.cs.ucsb.edu/~urs/oocsb/papers/oopsla96.pdf

Вот еще один ресурс, который рассматривает побочный эффект наличия большой библиотеки виртуальных классов http://keycorner.org/pub/text/doc/kde-slow.txt

Отправка виртуальных вызовов с несколькими наследованиями специфична для компилятора, поэтому реализация также будет иметь эффект в этом случае.

Что касается вашего конкретного вопроса о наличии большого количества базовых классов, обычно в макете памяти объекта класса будут иметься vtbl ptrs для всех других составных классов внутри него.

Проверьте эту страницу для образца шаблона vtable - http://www.codesourcery.com/public/cxx-abi/cxx-vtable-ex.html

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

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

Ответ 6

Почти все ответы указывают на то, будут ли виртуальные методы более медленными в примере OP, но я думаю, что OP просто спрашивает, медленно ли имеет несколько уровней наследования и сам по себе. Ответ, конечно, нет, поскольку все это происходит во время компиляции в С++. Я подозреваю, что вопрос основан на опыте с языками script, где такие графики наследования могут быть динамическими, и в этом случае он потенциально может быть медленнее.

Ответ 7

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

Брайан, чтобы быть ясным и ответить на ваш комментарий. Если в вашем дереве наследования нет виртуальных функций, это не влияет на производительность.

Ответ 8

Наличие нетривиальных конструкторов в дереве с глубоким наследованием может замедлить создание объекта, когда каждое создание дочернего объекта приводит к вызову функций всем родительским конструкторам вплоть до базы.

Ответ 9

Да, если вы ссылаетесь на это так:

// F is-a E,
// E is-a D and so on

A* aObject = new F(); 
aObject->CallAVirtual();

Затем вы работаете с указателем на объект типа A. Учитывая, что вы вызываете функцию, которая является виртуальной, она должна искать таблицу функций (vtable), чтобы получить правильные указатели. Есть некоторые накладные расходы на это, да.

Ответ 10

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

Но это не разница, о которой вы обычно должны беспокоиться.

Ответ 11

Как указано Corey Ross, vtable известна во время компиляции для любого производного класса, и поэтому стоимость виртуального вызова действительно должна быть одинаковой независимо структуры иерархии.

Это, однако, нельзя сказать для dynamic_cast. Если вы рассмотрите способ реализации dynamic_cast, базовый подход будет иметь поиск O (n) по вашей иерархии!

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

sturct A { int i; };
struct B { int j; };

struct C : public A, public B { int k ; };

// Let assume that the layout of C is:  { [ int i ] [ int j ] [int k ] }

void foo (C * c) {
  A * a = c;                // Probably has zero cost
  B * b = c;                // Compiler needed to add sizeof(A) to 'c'
  c = static_cast<B*> (b);  // Compiler needed to take sizeof(A)' from 'b'
}

Ответ 12

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

Однако, если вы вызываете функции в базовом классе, которые вызывают функции в базовом классе и т.д., это может повлиять на производительность.

В сущности, это сильно зависит от того, как вы структурировали дерево наследования.