2014-10-21 16 views
11

Sto studiando i mixin (in C++). Ho letto alcuni articoli sui mix e ho trovato due diversi pattern di "approssimativi" mixin in C++.Due diversi pattern di mixin in C++. (mixin? CRTP?)

Motivo 1:

template<class Base> 
struct Mixin1 : public Base { 
}; 

template<class Base> 
struct Mixin2 : public Base { 
}; 

struct MyType { 
}; 

typedef Mixin2<Mixin1<MyType>> MyTypeWithMixins; 

Campione 2: (può essere chiamata CRTP)

template<class T> 
struct Mixin1 { 
}; 

template<class T> 
struct Mixin2 { 
}; 

struct MyType { 
}; 

struct MyTypeWithMixins : 
    public MyType, 
    public Mixin1<MyTypeWithMixins>, 
    public Mixin2<MyTypeWithMixins> { 
}; 

Sono loro equivalente in pratica? Mi piacerebbe conoscere la differenza pratica tra i modelli.

risposta

7

La differenza è la visibilità. Nel primo pattern, i membri di MyType sono direttamente visibili e utilizzabili dai mixin, senza alcuna necessità di casting, e i membri di Mixin1 sono visibili a Mixin2. Se MyType desidera accedere ai membri dei mix, è necessario eseguire il cast this e non c'è un ottimo modo per farlo in modo sicuro.

Nel secondo modello, non c'è visibilità automatica tra il tipo e le mixins, ma le mixins può tranquillamente e facilmente gettato this per MyTypeWithMixins ed accedere così i membri del tipo e di altre mixin. (MyType potrebbe anche, se applicato anche il CRTP.)

Quindi si tratta di convenienza rispetto alla flessibilità. Se i tuoi mixin stanno semplicemente accedendo ai servizi dal tipo e non hanno dipendenze di pari livello, il primo modello è semplice e diretto. Se un mixin dipende dai servizi forniti dal tipo o da altri mixin, sei più o meno costretto a usare il secondo pattern.

7

Sono equivalenti praticamente? Mi piacerebbe conoscere la differenza pratica tra i modelli.

Sono diversi concettualmente.

Per il primo schema, i decoratori seguono (in modo trasparente) una classe di funzionalità di base, ognuno dei quali aggiunge la propria torsione/specializzazione a un'implementazione esistente.

Il rapporto dei primi modelli modello è "is-a" (MyTypeWithMixins è una specializzazione Mixin1<MyType>, Mixin1<MyType> è una specializzazione MyType).

Questo è un buon approccio quando si implementano funzionalità all'interno di un'interfaccia rigida (poiché tutti i tipi implementeranno la stessa interfaccia).

Per il secondo modello, si dispone di parti di funzionalità utilizzate come dettagli di implementazione (possibilmente all'interno di classi diverse non correlate).

Il rapporto modellato ecco "è implementato in termini di" (MyTypeWithMixins è una specializzazione MyType, implementato in termini diMixin1 e Mixin2 funzionalità). In molte implementazioni CRTP, la base basata su CRTP è ereditata come privata o protetta.

Questo è un buon approccio quando si implementano funzionalità comuni su diversi componenti indipendenti (ad esempio, non con la stessa interfaccia).Questo perché due classi che ereditano da Mixin1 saranno non con la stessa classe base.

Per fornire un esempio concreto per ciascuna:

Per il primo caso, considerare la modellazione di una libreria GUI. Ogni controllo visivo avrebbe una (per esempio) funzione display, che in uno ScrollableMixin aggiungerebbe barre di scorrimento, se necessario; Il barre di scorrimento mixin sarebbe una classe base per la maggior parte dei controlli che vengono ri-considerevole (ma tutti una parte della gerarchia "controllo/componente visivo/visualizzabile" classe.

class control { 
    virtual void display(context& ctx) = 0; 
    virtual some_size_type display_size() = 0; 
}; 

template<typename C>class scrollable<C>: public C { // knows/implements C's API 
    virtual void display(context& ctx) override { 
     if(C::display_size() > display_size()) 
      display_with_scrollbars(ctx); 
     else 
      C::display(canvas); 
    } 
    ... 
}; 

using scrollable_messagebox = scrollable<messagebox>; 

In questo caso, tutti i tipi mixin sostituirà (ad esempio) un metodo di visualizzazione e delegherà parti della sua funzionalità (la parte di disegno specializzata) al tipo decorato (la base)

Per il secondo caso, considerare un caso in cui si implementerebbe un sistema interno per aggiungere un numero di versione agli oggetti serializzati all'interno dell'applicazione.L'implementazione sarebbe simile a questa:

template<typename T>class versionable<T> { // doesn't know/need T's API 
    version_type version_; 
protected: 
    version_type& get_version(); 
}; 

class database_query: protected versionable<database_query> {}; 
class user_information: protected versionable<user_information> {}; 

In questo caso, sia database_query sia user_information memorizzano le loro impostazioni con un numero di versione, ma non sono in alcun modo nella stessa gerarchia di oggetti (non hanno una base comune).

+0

Questo non è ciò che CRTP è per. Certo, CRTP coinvolge la struttura generale 'classe Child: Parent ', ma il punto intero è che la classe base - 'Parent ', conosce la classe figlio' Child' (come? È un parametro template!) Parent può quindi fai riferimento alle cose definite nel bambino stesso - ad esempio, può creare un operatore! = dall'operatore della classe figlio ==. –

+0

@HWalters, non necessariamente. Nell'esempio che ho fornito sopra, l'implementazione di 'versionable ' non impone alcuna restrizione su 'T' (cioè, non ha bisogno di sapere nulla a riguardo). Questo è stato fatto apposta. – utnapistim

+0

Ho sbagliato; usando 'Child' per istanziare' Parent' semplicemente come un univoco conveniente per il tipo di base ha utilità. Ma penso che abbiate sostenuto il punto sbagliato: non imporre restrizioni su "T" è insufficiente per giustificare l'uso del CRTP. In particolare, lotto con il tuo esempio; il risultato finale è che gli oggetti 'database_query' e' user_information' hanno membri version_ e get_version, anche dello stesso tipo. Quindi quale vantaggio viene trasmesso utilizzando il CRTP per iniettarli? (Cioè, certo, le classi base sono _ diversi tipi, ma in che modo utile sarebbe quella?) –