2013-02-14 9 views
6

Ora, so che non è generalmente opportuno aggiungere nuove funzioni virtuali alle classi non foglia in quanto interrompe la compatibilità binaria per tutte le classi derivate che non sono state ricompilate. Tuttavia, ho una situazione un po 'diversa:Pure funzioni virtuali e compatibilità binaria

devo una classe di classe di interfaccia e l'implementazione compilato in una libreria condivisa, ad esempio:

class Interface { 
    public: 
     static Interface* giveMeImplPtr(); 
     ... 
     virtual void Foo(uint16_t arg) = 0; 
     ... 
} 

class Impl { 
    public: 
     ... 
     void Foo(uint16_t arg); 
     .... 
} 

La mia domanda principale utilizza questa libreria condivisa, e potrebbe essere sostanzialmente scritto come:

Interface* foo = Implementation::giveMeImplPtr(); 
foo->Foo(0xff); 

In altre parole, l'applicazione non ha alcuna classi che derivano da Interface, si limita lo utilizza.

Ora, dire che voglio sovraccaricare Foo(uint16_t arg) con Foo(uint32_t arg), io sono sicuro di fare:

class Interface { 
    public: 
     static Interface* giveMeImplPtr(); 
     ... 
     virtual void Foo(uint16_t arg) = 0; 
     virtual void Foo(uint32_t arg) = 0; 
     ... 
} 

e ricompilare la mia libreria condivisa senza dover ricompilare l'applicazione?

In tal caso, sono presenti avvertenze inusuali di cui ho bisogno di essere a conoscenza? In caso contrario, ho altre opzioni oltre a prendere la versione hit e up della libreria, rompendo così la compatibilità all'indietro?

risposta

5

ABI dipende fondamentalmente dalla dimensione e dalla forma dell'oggetto, compreso il vtable. L'aggiunta di una funzione virtuale cambierà definitivamente il vtable e il modo in cui cambia dipende dal compilatore.

In questo caso, qualcos'altro da considerare è che non stai solo proponendo una modifica ABI, ma un'interruzione API molto difficile da rilevare in fase di compilazione. Se questi non fossero funzioni virtuali e la compatibilità ABI non era un problema, dopo la modifica, qualcosa di simile:

void f(Interface * i) { 
    i->Foo(1) 
} 

tranquillamente finire per chiamare la nuova funzione, ma solo se tale codice è ricompilato, che può fare il debug molto difficile.

5

La semplice risposta è: no. Ogni volta che cambi la definizione della classe allo al, potresti perdere la compatibilità binaria. L'aggiunta di una funzione non virtuale o di membri statici di solito è sicura nella pratica, sebbene sia ancora formalmente un comportamento indefinito, ma lo è . Qualunque altra cosa probabilmente comprometterà la compatibilità binaria .

2

È stato molto bello per me quando mi trovavo in una situazione simile e ho trovato che MSVC inverte l'ordine delle funzioni sovraccariche. Secondo il vostro esempio, MSVC costruirà la v_table (in binario) come questo:

virtual void Foo(uint32_t arg) = 0; 
virtual void Foo(uint16_t arg) = 0; 

Se ci allarghiamo un po 'il vostro esempio, in questo modo:

class Interface { 
    virtual void first() = 0; 
    virtual void Foo(uint16_t arg) = 0; 
    virtual void Foo(uint32_t arg) = 0; 
    virtual void Foo(std::string arg) = 0; 
    virtual void final() = 0; 
} 

MSVC costruirà il seguente v_table :

virtual void first() = 0; 
    virtual void Foo(std::string arg) = 0; 
    virtual void Foo(uint32_t arg) = 0; 
    virtual void Foo(uint16_t arg) = 0; 
    virtual void final() = 0; 

Borland Builder e GCC non cambiare l'ordine, ma

  1. Essi non questo che le versioni, che ho provato
  2. Se la libreria compilata da GCC (per esempio), e applicazione sarà compilato da MSVC, sarebbe un epic fail

Un fine ... Mai fare affidamento sulla compatibilità binaria. Qualsiasi cambio di classe deve far ricompilare tutto il codice, utilizzandolo.

2

Si sta cercando di descrivere il popolare "Make classi non derivabili" tecnica per conservare la compatibilità binaria che viene utilizzato, ad esempio, nel Symbian C++ API (cercare NewL metodo fabbrica):

  1. Fornire una funzione di fabbrica;
  2. Dichiarare C++ costruttore privato (e non esportato non in linea, e la classe non dovrebbe avere classi amico o funzioni), questo rende la classe non derivabili e poi si può:

    • Aggiungere virtuale funzioni alla fine della dichiarazione della classe,
    • Aggiungere membri dati e modificare la dimensione della classe.

Questa tecnica funziona solo per GCC compilatore perché consente di risparmiare l'ordine fonte di funzioni virtuali a livello binario.

Spiegazione

funzioni virtuali vengono richiamati dal offset nel v-table di un oggetto, non per il nome storpiato. Se è possibile ottenere il puntatore dell'oggetto solo chiamando un metodo factory statico e conservando l'offset di tutte le funzioni virtuali (salvando l'ordine sorgente, aggiungendo nuovi metodi alla fine), questo sarà compatibile con i binari a ritroso.

La compatibilità sarà rotto se la classe ha un costruttore pubblico (in linea o non in linea):

  • linea: applicazioni copiare un vecchio disegno v-table e la memoria antica della classe che sarà diverso da quelli usati nella nuova libreria; se si chiama un metodo esportato o si passa un oggetto come argomento a tale metodo, ciò potrebbe causare un danneggiamento della memoria dell'errore di segmentazione;

  • non in linea: la situazione è migliore, perché si può cambiare v-table con l'aggiunta di nuovi metodi virtuali al fine della dichiarazione di classe foglia, perché il linker riposizionare il layout di v-table delle classi derivate a il lato client se caricherete la nuova versione della libreria; ma non è ancora possibile modificare la dimensione della classe (ad esempio aggiungendo nuovi campi), poiché la dimensione è hardcoded al momento della compilazione e chiamare un costruttore di nuova versione può rompere la memoria degli oggetti vicini sullo stack o sull'heap del client.

Strumenti

tenta di utilizzare lo strumento abi-compliance-checker per controllare a ritroso compatibilità binaria delle vostre versioni di libreria di classe su Linux.