Consideriamo il più vicino possibile a un confronto tra mele e mele, il che significa che stai usando questa classe base solo per derivarla una volta in una classe che non è pubblicamente visibile e attualizzando l'estensibilità di questa soluzione. L'unico scopo di usare un'astrazione qui è quello di nascondere un'implementazione concreta, niente di più (come nel caso del pimpl).
Quindi abbiamo qualcosa di simile a questo (interfaccia astratta):
// In some header file:
class IFoo
{
public:
virtual ~IFoo() {}
virtual void do_something() = 0;
};
std::unique_ptr<IFoo> create_foo();
// Inside source file (hidden from the outside world):
class Foo: public IFoo
{
...
};
std::unique_ptr<IFoo> create_foo()
{
return std::unique_ptr<IFoo>(new Foo);
}
vs. Pimpl:
// In some header file:
class Foo
{
public:
Foo();
Foo(const Foo& other);
Foo& operator=(const Foo& other);
~Foo();
void do_something();
private:
struct Impl;
std::unique_ptr<Impl> priv;
};
// Inside source file (hidden from the outside world):
struct Foo::Impl
{
...
};
Foo::Foo(): priv(new Impl)
{
}
...
In Common
Ciò che entrambi hanno in comune è che servono la stessa premessa fondamentale: nascondono i dettagli di implementazione dal mondo esterno, riducono le dipendenze in fase di compilazione.
L'interfaccia astratta probabilmente lo fa ancora di più, non esponendo nemmeno un Pimpl, anche se in genere questo sta entrando in un territorio davvero terrificante. Potrebbe essere un'estetica favorevole per alcuni, rendendo visibile solo l'interfaccia pubblica / documentazione nell'intestazione (se l'estetica dell'intestazione è valutata).
Entrambi soffrono della necessità di allocazione dinamica, di un puntatore in più e di una rappresentazione della memoria disgiunta dal puntatore al pointee, nonché di una potenziale perdita di localizzazione spaziale da un poetae all'altro. L'inefficienza della memoria può essere mitigata considerevolmente per entrambi i casi da un allocatore fisso per raggruppare blocchi di memoria da un pool contiguo per i pointees da utilizzare.
Diverso
Alcune cose diverse tra queste due:
- La versione dell'interfaccia astratta richiede un overhead di dispacciamento dinamico (alias virtuale) per ogni funzione. Questo può essere discutibile per condutture più profonde dove viene eseguita una quantità ragionevole di lavoro in una funzione membro. Può trasformarsi in un hotspot minor per le funzioni dei membri adolescenti chiamate in numeri enormi (es: milioni e milioni di volte) in loop stretti per alcune semplici funzioni di accesso (sia a causa della pipeline poco profonda che della barriera di ottimizzazione che impedisce l'inlining). Generalmente non mi preoccuperei di queste troppe pre-misure, dato che hai sempre la possibilità di trasformare
IFoo
da una pura interfaccia in una classe base astratta e di integrare alcune funzioni critiche se assolutamente necessario senza ridisegnare il tutto.
- La versione astratta richiede un overhead
vptr
per istanza e un vtable
da generare per classe.
- La versione di pimpl può fornire valore / copia / spostare la semantica direttamente senza la necessità di un altro wrapper (la versione astratta lo richiederebbe, insieme a metodi virtuali come
clone
o funzioni esterne come clone
implementate all'interno il file sorgente dove Foo
è visibile).
- La versione astratta è probabilmente un po 'più semplice da implementare (e probabilmente anche da leggere), in quanto consente di evitare di gestire la gestione separata di questo pimpl archiviando i dettagli di implementazione e consente semplicemente di definire una nuova classe e sovrascrivere alcune virtual funziona tutto in una volta.
- La versione astratta è un po 'più flessibile (es .: possiamo avere due o più implementazioni concrete separate di
IFoo
che vivono nella stessa base di codice). Questo sta allontanando dallo sforzo per il confronto tra mele e mele, tuttavia, notando che si tratta di mele alle arance.
- La versione astratta ha un ABI più fragile, poiché l'aggiunta di qualsiasi nuova funzione virtuale in qualsiasi punto interromperà l'ABI vtable. Questo è importante solo in contesti come i kit di sviluppo software usati dagli sviluppatori di plug-in in cui i binari non vengono tutti costruiti / distribuiti insieme.
Penso che riguardi le principali differenze. Nota che l'affettamento degli oggetti non è un problema qui a condizione che IFoo
non possa essere istanziato da solo.