2013-08-25 18 views
8

Se io definisco una classe come questa:Può funzioni virtuali essere inline

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

Vuol dire che che il distruttore virtuale e func sono inline

+0

Se ci pensate, l'integrazione di funzioni virtuali non ha molto senso. L'unico caso che posso vedere è se conosci il tipo in fase di compilazione, ma anche in questo caso non sono sicuro che un compilatore possa fare l'ottimizzazione. – Borgleader

+0

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

+0

@Borgleader: lo fanno, quando possono.Tuttavia nessun compilatore è veramente bravo a causa di regole complesse nel linguaggio C++ riguardanti la costruzione e la distruzione di oggetti polimorfici. Inoltre, poiché in generale non esiste JIT, il solo sottoinsieme di situazioni in cui può essere fatto è limitato. –

risposta

10

Sia il compilatore sceglie di inline una funzione che è definita inline è interamente a carico del compilatore. In generale, le funzioni virtual possono essere solo sottolineate quando il compilatore può provare che il tipo statico corrisponde al tipo dinamico o quando il compilatore può determinare in sicurezza il tipo dinamico. Ad esempio, quando si utilizza un valore di tipo A, il compilatore sa che il tipo dinamico non può essere diverso e può integrare la funzione. Quando si utilizza un puntatore o un riferimento il compilatore in genere non è in grado di dimostrare che il tipo statico è lo stesso e le funzioni virtual generalmente devono seguire la consueta distribuzione virtuale. Tuttavia, anche quando viene utilizzato un puntatore, il compilatore può disporre di informazioni sufficienti dal contesto per conoscere il tipo dinamico esatto. Ad esempio, MatthieuM. dato il seguente exmaple:

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

In questo caso il compilatore può stabilire che a punta a un oggetto B e, quindi, chiama il versione corretta di func() senza distribuzione dinamica. Senza la necessità della spedizione dinamica, func() potrebbe essere inline. Ovviamente, se i compilatori fanno l'analisi corrispondente dipende dalla sua rispettiva implementazione.

Come giustamente sottolineato da hvd, il dispatch virtuale può essere eluso chiamando una funzione virtuale con piena qualificazione, ad esempio a->A::func(), nel qual caso la funzione virtuale può essere anche inline. Il motivo principale per cui le funzioni virtuali non sono generalmente in linea è la necessità di eseguire un dispatch virtuale. Con la piena qualifica, tuttavia, la funzione da chiamare è nota.

+3

Una chiamata non virtuale a una funzione virtuale ('a-> A :: func()') è un altro esempio ovvio in cui l'inlining generalmente funziona. – hvd

+0

Vedo il link @Mat dato, sembra che il distruttore virtuale in linea abbia senso, ma sono ancora un po 'confuso su come i distruttori sono inarcati – Ghostblade

+2

* quando il compilatore può dimostrare che il tipo statico corrisponde al tipo dinamico *: in realtà più complicato di così. Considera 'Base * b = nuovo Derivato {}; b-> func(); ', qui la chiamata può essere inline se il compilatore è abbastanza intelligente da comprendere che il tipo dinamico di' b' è necessariamente 'Derivato'. Clang è un compilatore così intelligente. –

4

Sì, e in più modi. Potete vedere alcuni esempi di deviazione di in this email Ho inviato alla mailing list di Clang circa 2 anni fa.

Come tutte le ottimizzazioni, questo è in sospeso le capacità del compilatore per eliminare le alternative: se può dimostrare che la chiamata virtuale è sempre risolta in Derived::func allora può chiamarlo direttamente.

Ci sono varie situazioni, cominciamo prima con le prove semantiche:

  • SomeDerived& d dove SomeDerived è final permette di devirtualization di tutto il metodo chiamate
  • SomeDerived& d, dove foo è final permette anche devirtualization di questa particolare chiamata

Quindi, ci sono situazioni in cui si conosce il tipo dinamico dell'oggetto:

  • SomeDerived d; => il tipo dinamico di d è necessariamente SomeDerived
  • SomeDerived d; Base& b; => il tipo dinamico di b è necessariamente SomeDerived

Coloro 4 devirtualization le situazioni sono generalmente risolte dal front-end del compilatore perché richiedono una conoscenza fondamentale della semantica del linguaggio. Posso attestare che tutti e 4 sono implementati in Clang e penserei che siano implementati anche in gcc.

Tuttavia, ci sono molte situazioni in cui questo si rompe:

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); 
} 

Anche se qui è evidente che la chiamata a foo viene risolto Derived::foo, Clang/LLVM non ottimizzarlo. Il problema è che:

  • Clang (front-end) non esegue inline, quindi non può sostituire print(d) da e devirtualize la chiamata
  • LLVM (back-end) non conosce la semantica del linguaggio , quindi anche dopo la sostituzione print(d) da assume che il puntatore virtuale di d potrebbe essere stata modificata dalla opaque (la cui definizione è opaco, come suggerisce il nome)

ho seguito sforzi sulla maili Clang e LLVM ng list come entrambi gli insiemi di sviluppatori hanno ragionato sulla perdita di informazioni e su come ottenere Clang per dire a LLVM: "va tutto bene" ma sfortunatamente il problema è non banale e non è ancora stato risolto ... quindi la semi-assediva deviazione in il front-end per cercare di ottenere tutti i casi più ovvi, e alcuni meno ovvi (anche se, per convenzione, il front-end non è dove li implementate).


Per riferimento, il codice per il devirtualization in Clang può essere trovato in CGExprCXX.cpp in una funzione chiamata canDevirtualizeMemberFunctionCalls. È lungo solo ~ 64 righe (in questo momento) e completamente commentato.

+0

+1 per il riferimento al codice. – Surt