Подтвердить что ты не робот

Есть ли какой-либо штраф/стоимость виртуального наследования в С++ при вызове метода не виртуального базиса?

Имеет ли использование виртуального наследования в С++ ограничение времени выполнения в скомпилированном коде, когда мы вызываем регулярного члена функции из его базового класса? Пример кода:

class A {
    public:
        void foo(void) {}
};
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};

// ...

D bar;
bar.foo ();
4b9b3361

Ответ 1

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

void f(B* p) { p->foo(); }

void g()
{
    D bar;
    f(&bar);
}

Предполагая, что вызов f не указан, компилятору необходимо сгенерировать код, чтобы найти местоположение субобъекта виртуального базового класса A, чтобы вызвать foo. Обычно этот поиск включает проверку vptr/vtable.

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

Ответ 2

Да, виртуальное наследование имеет служебные данные о производительности во время выполнения. Это связано с тем, что компилятор для любого указателя/ссылки на объект не может найти его под-объекты во время компиляции. В constrast, для одиночного наследования, каждый под-объект находится со статическим смещением исходного объекта. Рассмотрим:

class A { ... };
class B : public A { ... }

Макет памяти B выглядит примерно так:

| B stuff | A stuff |

В этом случае компилятор знает, где находится A. Однако теперь рассмотрим случай MVI.

class A { ... };
class B : public virtual A { ... };
class C : public virtual A { ... };
class D : public C, public B { ... };

B макет памяти:

| B stuff | A stuff |

C:

| C stuff | A stuff |

Но подождите! Когда D создается, это не выглядит так.

| D stuff | B stuff | C stuff | A stuff |

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

Ответ 3

По крайней мере, в типичной реализации виртуальное наследование несет (небольшое!) штраф за (по крайней мере некоторый) доступ к членам данных. В частности, вы обычно получаете дополнительный уровень косвенности для доступа к элементам данных объекта, из которого вы получили практически. Это происходит потому, что (по крайней мере, в нормальном случае) два или более отдельных производных класса имеют не только один и тот же базовый класс, но тот же объект базового класса. Для этого оба производных класса имеют указатели на одно и то же смещение в наиболее производный объект и получают доступ к этим элементам данных через этот указатель.

Хотя это технически не из-за виртуального наследования, вероятно, стоит отметить, что существует отдельное (опять же небольшое) наказание за множественное наследование вообще. В типичной реализации одиночного наследования у вас есть указатель vtable при некотором фиксированном смещении в объекте (нередко с самого начала). В случае множественного наследования у вас, очевидно, не может быть двух указателей vtable при одном и том же смещении, поэтому вы получите несколько указателей vtable, каждый с отдельным смещением в объекте.

IOW, указатель vtable с одиночным наследованием обычно равен static_cast<vtable_ptr_t>(object_address), но с множественным наследованием вы получаете static_cast<vtable_ptr_t>(object_address+offset).

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

Ответ 4

Я думаю, что для виртуального наследования нет времени исполнения. Не путать виртуальное наследование с виртуальными функциями. Обе эти две вещи.

виртуальное наследование гарантирует, что у вас есть только один под-объект A в случаях D. Поэтому я не думаю, что для него будет .

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

Ответ 5

Конкретно в Microsoft Visual С++ существует реальная разница в размерах указателя на элемент. См. # pragma pointers_to_members. Как вы можете видеть в этом списке - самым общим методом является "виртуальное наследование", которое отличается от множественного наследования, которое, в свою очередь, отличается от одиночного наследования.

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