2013-08-25 2 views
8

Если я определить класс, как это:Может виртуальные функции быть встраиваемыми

class A{ 
public: 
    A(){} 
    virtual ~A(){} 
    virtual void func(){} 
}; 

Означает ли это, что виртуальный деструктор и func являются встраиваемыми

+0

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

+0

http://stackoverflow.com/questions/733737/are-inline-virtual-functions-really-a-non-sense?rq=1 – Mat

+0

@Borgleader: они делают, когда могут.Однако компилятор действительно не хорош в этом из-за сложных правил на языке C++ в отношении построения и уничтожения полиморфных объектов. Кроме того, поскольку в целом нет JITing, очень подмножество ситуаций, когда это может быть сделано, ограничено. –

ответ

10

Решит ли компилятор встраивать функцию, которая определена inline полностью зависит от компилятора. В общем случае функции virtual могут быть только встроены, когда компилятор может либо доказать, что статический тип соответствует динамическому типу, либо когда компилятор может безопасно определить динамический тип. Например, когда вы используете значение типа A, компилятор знает, что динамический тип не может быть другим, и он может встроить функцию. При использовании указателя или ссылки компилятор обычно не может доказать, что статический тип тот же, и функции virtual обычно должны следовать обычной виртуальной диспетчеризации. Однако даже когда используется указатель, у компилятора может быть достаточно информации из контекста, чтобы узнать точный динамический тип. Например, MatthieuM. дал следующую exmaple:

A* a = new B; 
a->func(); 

В этом случае компилятор может определить, что a указывает на B объекта и, таким образом, вызвать правильную версию func() без динамической диспетчеризации. Без необходимости динамической отправки func() может быть встроен. Конечно, независимо от того, соответствуют ли компиляторы соответствующий анализ, зависит от его реализации.

Как указано в правильном направлении, виртуальная отправка может быть обойдена вызовом виртуальной функции, которая будет иметь полную квалификацию, например a->A::func(), и в этом случае виртуальная функция также может быть встроена. Основная причина, по которой виртуальные функции, как правило, не включены, - это необходимость виртуальной отправки. Однако при полной квалификации функция, которую нужно назвать, известна.

+3

Не виртуальный вызов виртуальной функции ('a-> A :: func()') является еще одним очевидным примером, в котором обычно работает inlining. – hvd

+0

Я смотрю ссылку @Mat дал, кажется, что встроенный виртуальный деструктор имеет смысл, но я все еще немного запутался в том, как деструкторы встроены – Ghostblade

+2

* когда компилятор может доказать, что статический тип соответствует динамическому типу *: это на самом деле более сложный. Рассмотрим 'Base * b = new Derived {}; b-> func(); ', здесь вызов может быть встроен, если компилятор достаточно умен, чтобы понять, что динамический тип' b' обязательно является 'Derived'. Clang - такой умный компилятор. –

4

Да, и несколькими способами. Вы можете увидеть некоторые примеры devirtualizationin this email Я отправил в список рассылки Clang около 2 лет назад.

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

Существуют различные ситуации, давайте начнем сначала с семантическими доказательствами:

  • SomeDerived& d где SomeDerived является final позволяет девиртуализации всех вызовов метода
  • SomeDerived& d, где foo является final также позволяет девиртуализации из данный конкретный звонок

Затем возникают ситуации где вы знаете, динамический тип объекта:

  • SomeDerived d; => динамического типа d обязательно SomeDerived
  • SomeDerived d; Base& b; => динамического типа b обязательно SomeDerived

Тех 4 Реала ситуации обычно решаются компилятором, потому что они требуют фундаментальных знаний о семантике языка. Я могу подтвердить, что все 4 реализованы в Clang, и я думаю, что они также реализованы в gcc.

Однако существует множество ситуаций, когда это ломается:

struct Base { virtual void foo() = 0; }; 
struct Derived: Base { virtual void foo() { std::cout << "Hello, World!\n"; }; 

void opaque(Base& b); 
void print(Base& b) { b.foo(); } 

int main() { 
    Derived d; 

    opaque(d); 

    print(d); 
} 

Даже если здесь, очевидно, что призыв к foo разрешен к Derived::foo, Clang/LLVM не будет оптимизировать его. Вопрос заключается в том, что:

  • Clang (передний конец) не выполняет подстановку, таким образом, она не может заменить print(d) на и devirtualize на вызов
  • LLVM (фоновым) не знает семантики языка , таким образом, даже после замены print(d) на он предполагает, что виртуальный указатель d мог быть изменен opaque (определение которого является непрозрачным, как следует из названия)

Я следовал усилия на Майли Clang и LLVM ng list, поскольку оба набора разработчиков рассуждали о потере информации и о том, как заставить Clang рассказать LLVM: «все в порядке», но, к сожалению, проблема нетривиальна и еще не решена ... таким образом, наполовину освидетельствование в интерфейсом, чтобы попытаться получить все очевидные случаи, а некоторые из них не столь очевидны (хотя, по соглашению, интерфейс не там, где вы их реализуете).


Для справки, код девиртуализации в Clang могут быть найдены в CGExprCXX.cpp в функции под названием canDevirtualizeMemberFunctionCalls. Это всего лишь ~ 64 строки (прямо сейчас) и тщательно прокомментированы.

+0

+1 для ссылки на код. – Surt

Смежные вопросы