Dependency Inversion è un'alternativa valida all'idioma pImpl?

4

Se voglio presentare all'utente del mio codice un'interfaccia, che è improbabile che cambi, posso scrivere solo la parte public delle classi nelle intestazioni pubbliche e avere un singolo puntatore privato su un altro oggetto, che detiene le dichiarazioni private. Credo che questo sia noto come l'idioma pImpl.

Ora immagina un altro approccio. La parte pubblica della classe viene inserita in IClass o ClassBase o in qualsiasi classe, posizionata nelle intestazioni pubbliche. Quindi, in un'intestazione privata (== non esposta all'utente), questa classe base viene ereditata e vengono implementati i metodi.

A me sembra che il secondo approccio raggiunga gli stessi obiettivi del primo. Ma ci sono degli svantaggi? Quando preferire quale?

    
posta Vorac 13.03.2015 - 18:10
fonte

2 risposte

2

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.

    
risposta data 03.01.2016 - 00:53
fonte
4

Con Pimpl, l'altro codice è libero di creare istanze e utilizzare la classe normalmente. Dall'esterno sembra esattamente come una classe normale. Questo è abbastanza conveniente per un utente, ma c'è una penalità di efficienza.

Con l'intera implementazione della classe come privata, il tuo codice dovrà fornire all'utente un modo per creare istanze: un metodo factory o qualcosa di simile. Inoltre, tutte le chiamate al metodo dovranno essere virtuali e il codice client dovrà utilizzarlo come riferimento. Senza conoscere il tipo e la dimensione effettivi (ecc.) Della variabile, non può essere allocato nello stack o passato per valore.

Entrambi gli approcci proteggono il codice del client da modifiche all'implementazione ma non penso che tu possa dire che sono esattamente equivalenti. Quello che usi dipende dall'uso previsto della classe e personalmente ho trovato usi per entrambi i pattern all'interno dello stesso progetto.

    
risposta data 13.03.2015 - 19:16
fonte

Leggi altre domande sui tag