Бывают ли случаи, когда класс объявляет виртуальные методы и компилятору не нужно использовать vptr?

StackOverflow https://stackoverflow.com/questions/2336107

  •  22-09-2019
  •  | 
  •  

Вопрос

Мне было интересно, существует ли возможная оптимизация, при которой компилятору не нужно назначать vptr экземпляру объекта, даже если тип объекта представляет собой класс с виртуальными методами.

Например, рассмотрим:

#include <iostream>
struct FooBase
{
  virtual void bar()=0;
};

struct FooDerived : public FooBase
{
  virtual void bar() { std::cout << "FooDerived::bar()\n"; }
};

int main()
{
   FooBase* pFoo = new FooDerived();
   pFoo->bar();

  return 0;
}

В этом примере компилятор наверняка знает, каким будет тип pFoo во время компиляции, поэтому ему не нужно использовать vptr для pFoo, верно?Есть ли более интересные случаи, когда компилятор может избежать использования vptr?

Это было полезно?

Решение

Опираясь на Ответ Эндрю Штейна, потому что я думаю, вы также хотите знать, когда можно избежать так называемых «накладных расходов виртуальных функций во время выполнения».(Накладные расходы есть, но они крошечный, и редко о чем стоит беспокоиться.)

Действительно трудно избежать космос указателя vtable, но сам указатель можно игнорировать, в том числе и в вашем примере.Потому что пФооинициализация находится в этой области, компилятор знает, что pFoo->bar должно означать Фудеривед::бар, и ему не нужно проверять vtable.Существует также несколько методов кэширования, позволяющих избежать многократного поиска в виртуальной таблице, от простых до сложных.

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

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

Вы используете очень простую программу в одном файле.

Представьте, что у вас есть FooBase и FooDerived в Foo.h и Foo.cpp и main в main.cpp.Как компилятору при компиляции Foo.cpp узнать, что во всей программе втбл не нужен.Он не видел main.cpp.

Окончательное решение может быть сделано только во время компоновки, когда уже слишком поздно и сложно изменить объектный файл, найти все вызовы sizeof(FooDerived) и т. д.

Даже самые современные компиляторы с глобальной оптимизацией по-прежнему придерживаются принципа «независимого перевода».Согласно этому принципу каждая единица трансляции компилируется независимо, без каких-либо сведений о каких-либо других единицах трансляции, которые могут существовать во всей конечной программе.

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

В вашем примере умный компилятор может определить, что динамический тип *pFoo объект FooDerived.Это позволит компилятору оптимизировать код:для создания прямого вызова FooDerived::bar функционировать в ответ на pFoo->bar() выражение (без использования виртуальной таблицы).Но, тем не менее, компилятору обычно все равно придется правильно инициализировать указатель виртуальной таблицы в каждом FooDerived объект.

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

Как говорили многие другие, если только компилятор не выполняет оптимизацию всей программы/времени компоновки (становится все более популярной;GCC получил это в версии 4.5), ему по-прежнему необходимо генерировать какую-то таблицу виртуальных функций для поддержки раздельной компиляции.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top