Se le classi con funzioni virtuali vengono implementate con vtables, come viene implementata una classe senza funzioni virtuali?

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

  •  01-07-2019
  •  | 
  •  

Domanda

In particolare, non dovrebbe esserci comunque una sorta di puntatore a funzione?

È stato utile?

Soluzione

Le funzioni membro non virtuali sono in realtà solo uno zucchero sintattico poiché sono quasi come una funzione ordinaria ma con controllo dell'accesso e un parametro oggetto implicito.

struct A 
{
  void foo ();
  void bar () const;
};

è sostanzialmente uguale a:

struct A 
{
};

void foo (A * this);
void bar (A const * this);

La vtable è necessaria per poter chiamare la funzione giusta per la nostra specifica istanza dell'oggetto.Ad esempio, se abbiamo:

struct A 
{
  virtual void foo ();
};

L'implementazione di 'foo' potrebbe approssimarsi a qualcosa del tipo:

void foo (A * this) {
  void (*realFoo)(A *) = lookupVtable (this->vtable, "foo");
  (realFoo)(this);   // Make the call to the most derived version of 'foo'
}

Altri suggerimenti

Penso che la frase "le classi con funzioni virtuali sono implementate con vtables"ti sta fuorviando.

La frase fa sembrare che siano implementate classi con funzioni virtuali "nel modo A" e vengono implementate classi senza funzioni virtuali "nel modo B".

In realtà, le classi con funzioni virtuali, inoltre essendo implementate come le classi, hanno anche una vtable.Un altro modo di vederlo è che "'vtables' implementa la parte 'funzione virtuale' di una classe".

Maggiori dettagli su come funzionano entrambi:

Tutte le classi (con metodi virtuali o non virtuali) sono struct.IL soltanto La differenza tra una struttura e una classe in C++ è che, per impostazione predefinita, i membri sono pubblici nelle strutture e privati ​​nelle classi.Per questo motivo utilizzerò qui il termine classe per riferirmi sia alle strutture che alle classi.Ricordate, sono quasi sinonimi!

Membri dati

Le classi sono (come le strutture) solo blocchi di memoria contigua in cui ogni membro è archiviato in sequenza.Tieni presente che a volte ci saranno degli spazi tra i membri per ragioni di architettura della CPU, quindi il blocco può essere più grande della somma delle sue parti.

Metodi

I metodi o le "funzioni membro" sono un'illusione.In realtà non esiste una “funzione membro”.Una funzione è sempre solo una sequenza di istruzioni in codice macchina archiviate da qualche parte nella memoria.Per effettuare una chiamata, il processore salta a quella posizione di memoria e inizia l'esecuzione.Si potrebbe dire che tutti i metodi e le funzioni sono "globali" e qualsiasi indicazione del contrario è una comoda illusione imposta dal compilatore.

Ovviamente, un metodo si comporta come se appartenesse a un oggetto specifico, quindi chiaramente c'è altro da fare.Per legare una particolare chiamata di un metodo (una funzione) a un oggetto specifico, ogni metodo membro ha un argomento nascosto che è un puntatore all'oggetto in questione.Il membro è nascosto nel senso che non lo aggiungi tu stesso al tuo codice C++, ma non c'è nulla di magico in questo: è molto reale.Quando dici questo:

void CMyThingy::DoSomething(int arg);
{
    // do something
}

Il compilatore Veramente fa questo:

void CMyThingy_DoSomething(CMyThingy* this, int arg)
{
    /do something
}

Infine, quando scrivi questo:

myObj.doSomething(aValue);

il compilatore dice:

CMyThingy_DoSomething(&myObj, aValue);

Non sono necessari puntatori a funzioni da nessuna parte!Il compilatore sa già quale metodo stai chiamando, quindi lo chiama direttamente.

I metodi statici sono ancora più semplici.Non hanno un Questo puntatore, quindi vengono implementati esattamente come li scrivi.

Questo è!Il resto è solo un comodo zucchero della sintassi:Il compilatore sa a quale classe appartiene un metodo, quindi si assicura di non consentire di chiamare la funzione senza specificare quale.Utilizza questa conoscenza anche per tradurre myItem A this->myItem quando è inequivocabile farlo.

(si, è esatto:l'accesso dei membri in un metodo è Sempre fatto indirettamente tramite un puntatore, anche se non ne vedi uno)

(Modificare:Rimossa l'ultima frase e pubblicata separatamente in modo che possa essere criticata separatamente)

I metodi virtuali sono necessari quando si desidera utilizzare il polimorfismo.IL virtual Il modificatore inserisce il metodo nel VMT per l'associazione tardiva e quindi in fase di esecuzione viene deciso quale metodo da quale classe viene eseguito.

Se il metodo non è virtuale, viene deciso in fase di compilazione da quale istanza di classe verrà eseguita.

I puntatori a funzione vengono utilizzati principalmente per i callback.

Se una classe con una funzione virtuale viene implementata con una vtable, una classe senza funzione virtuale viene implementata senza una vtable.

Una vtable contiene i puntatori a funzione necessari per inviare una chiamata al metodo appropriato.Se il metodo non è virtuale, la chiamata va al tipo noto della classe e non è necessario alcun riferimento indiretto.

Per un metodo non virtuale il compilatore può generare una normale invocazione di funzione (ad esempio, CALL a un particolare indirizzo con questo puntatore passato come parametro) o addirittura in linea.Per una funzione virtuale, il compilatore solitamente non sa in fase di compilazione a quale indirizzo richiamare il codice, pertanto genera codice che cerca l'indirizzo nella vtable in fase di esecuzione e quindi richiama il metodo.È vero, anche per le funzioni virtuali il compilatore a volte può risolvere correttamente il codice giusto in fase di compilazione (ad esempio, metodi su variabili locali invocate senza un puntatore/riferimento).

(Ho estratto questa sezione dalla mia risposta originale in modo che possa essere criticata separatamente.È molto più conciso e va al punto della tua domanda, quindi in un certo senso è una risposta molto migliore)

No, non ci sono puntatori a funzioni;invece, il compilatore risolve il problema alla rovescia.

Il compilatore chiama una funzione globale con un puntatore all'oggetto invece di chiamarne alcuni funzione puntata all'interno dell'oggetto

Perché?Perché di solito è molto più efficiente in questo modo.Le chiamate indirette sono istruzioni costose.

Non sono necessari puntatori a funzione poiché non possono cambiare durante il runtime.

I rami vengono generati direttamente nel codice compilato per i metodi;proprio come se avessi funzioni che non sono affatto in una classe, i rami vengono generati direttamente ad esse.

Il compilatore/linker collega direttamente quali metodi verranno invocati.Non è necessario un riferimento indiretto vtable.A proposito, cosa c'entra questo con "stack vs.mucchio"?

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top