2011-10-22 15 views
11

Voglio usare l'idioma pimpl per evitare che gli utenti della mia libreria abbiano bisogno delle nostre dipendenze esterne (come boost, ecc.) Tuttavia quando la mia classe è basata su modelli che sembra impossibile perché i metodi devono essere nel intestazione. C'è qualcosa che posso fare invece?pimpl per una classe di modelli

risposta

7

Se la classe è basata su modelli, gli utenti devono essenzialmente compilarlo (e questo è letteralmente vero nelle implementazioni C++ più utilizzate) e quindi hanno bisogno delle vostre dipendenze esterne.

La soluzione più semplice consiste nel mettere la maggior parte dell'implementazione della classe in una classe base non modello (o oggetto membro incapsulato di qualche classe). Risolvi il problema di nascondere il modulo lì.

E quindi scrivere la classe derivata (o allegata) per aggiungere sicurezza al tipo.

Ad esempio, supponiamo di avere un modello che fornisce la sorprendente capacità di allocare al primo accesso (omettendo il costruttore di copia necessario, l'assegnazione, distruttore):

template <class T> 
class MyContainer 
{ 
    T *instance_; 

public: 
    MyContainer() : instance_(0) {} 

    T &access() 
    { 
     if (instance_ == 0) 
      instance_ = new T(); 

     return *instance_; 
    } 
}; 

Se si voleva la "logica" essere separati in una classe di base non-modello, che avrebbe dovuto parametrizzare il comportamento in modo non-template, vale a dire, utilizzare le funzioni virtuali:

class MyBase 
{ 
    void *instance_; 

    virtual void *allocate() = 0; 

public: 
    MyBase() : instance_(0) {} 

    void *access() 
    { 
     if (instance_ == 0) 
      instance_ = allocate(); 

     return instance_; 
    } 
}; 

Quindi è possibile aggiungere il tipo di consapevolezzanello strato esterno:

template <class T> 
class MyContainer : MyBase 
{ 
    virtual void *allocate() 
     { return new T(); } 

public: 
    T &access() 
     { return *(reinterpret_cast<T *>(MyBase::access())); } 
}; 

cioè si utilizza funzioni virtuali che consente al modello di "riempire" le operazioni di tipo-dipendente. Ovviamente questo schema avrebbe davvero senso solo se si ha una logica di business che vale la pena di nascondersi.

+0

credo che questo approccio potrebbe essere utile anche se non si desidera che le definizioni del preprocessore ('# define'), costanti ecc per essere visibile. Come sviluppatore, non voglio vedere i dettagli di implementazione di una classe/libreria che sto usando, specialmente nella lista di completamento automatico. Potresti voler nascondere quelli anche se la tua logica aziendale non è degna di nascondersi. – mostruash

1

È possibile creare esplicitamente un'istanza di modelli nel file di origine, ma ciò è possibile solo se si conosce quale sarà il tipo di modello. Altrimenti, non usare l'idioma pimpl per i modelli.

Qualcosa di simile a questo:

header.hpp:

#ifndef HEADER_HPP 
#define HEADER_HPP 

template< typename T > 
class A 
{ 
    // constructor+methods + pimpl 
}; 

#endif 

sorgente.cpp:

#include "header.hpp" 

// implementation 

// explicitly instantiate for types that will be used 
template class A<int>; 
template class A<float>; 
// etc... 
+0

-1 Non utilizzare 'auto_ptr' per PIMPL (è ** comportamento non definito ** per istanziare' auto_ptr' con un tipo incompleto). Funziona se si definiscono sia il costruttore che il distruttore per la classe esterna. Ma in quel caso non hai bisogno di un puntatore intelligente. –

+0

@ AlfP.Steinbach unique_ptr? O solo un puntatore crudo? –

+1

Sì, sia OK (anche se non sono sicuro dei dettagli su come usare 'unique_ptr' in questo caso, vorrei solo usare' shared_ptr' e accettare il sovraccarico come il costo di non richiedere alle persone di leggere la stampa fine nel standard). Cheers, –

1

ci sono due soluzioni generali:

  • mentre l'interfaccia dipende da così il mio tipo T, rimanda a un'implementazione più debolmente tipizzata (ad es. uno usando i puntatori void* direttamente o tramite cancellazione del tipo) o

  • si supporta solo un numero specifico e piuttosto limitato di tipi.

La seconda soluzione è pertinente per es. char/wchar_t-roba indipendente.

La prima soluzione è stata abbastanza comune nei primi giorni di template C++, perché a quel tempo i compilatori non erano buone a riconoscere punti in comune nel codice macchina generato, e introdurrebbe cosiddetto “ codice bloat ”. Oggi, con grande sorpresa di qualsiasi novizio che lo provi, una soluzione basata su modelli può spesso avere un ingombro di codice macchina inferiore rispetto a una soluzione basata sul polimorfismo di runtime. Certo, YMMV.

Acclamazioni & hth.,

+0

La modifica tentata di qualcuno ha suggerito di rimuovere l'elaborazione "direttamente o tramite cancellazione di tipo" e "specifica e ..." qualificazione. L'elaborazione è necessaria per il significato e la qualifica è necessaria per la correttezza. L'attuale risposta accettata come soluzione è un esempio di "direttamente". Non c'è ancora nessun esempio di "cancellazione del tipo", e l'unica menzione è in questa risposta; sarebbe un peccato se qualcuno fosse riuscito a cancellarlo. –

Problemi correlati