2015-05-14 10 views
6

Ho la seguente classe di base template.Come forzare l'uso di modelli di template curiosamente ricorrenti in C++

template<typename T> 
class Base { 
    public: 
    void do_something() { 
    } 
}; 

Esso è destinato ad essere utilizzato come modello curiosamente ricorrenti modello. Dovrebbe essere ereditato come class B : public Base<B>. È necessario non essere ereditato come class B : public Base<SomeoneElse>. Voglio applicare staticamente questo requisito. Se qualcuno lo usa male, mi aspetto un errore nella fase di compilazione.

Quello che sto facendo è mettere un static_cast<T const&>(*this) in do_something(). In questo modo la classe che eredita il modello è o eredita dalla classe fornita come parametro del modello. Ci scusiamo per l'espressione confusa. In inglese semplice, richiede B o eredita da SomeoneElse in class B : public Base<SomeoneElse>.

Non so se è il modo ottimale per raggiungere questo obiettivo. Mi sembra schifoso.

Tuttavia, voglio fare di più. Voglio assicurarmi che lo B sia SomeoneElse stesso. Come lo posso fare?

+0

Il 'static_cast' non impone nulla; semplicemente provoca un comportamento indefinito se '* this' non è in realtà un' T'. –

+0

@ T.C. Effettua controlli statici. Quello di cui stai parlando dovrebbe essere 'reinterpret_cast'. –

+0

Controlla solo che 'T' è derivato da' Base ', non' * questo' è un 'T'. cioè, dato 'classe Bar: base pubblica {}; classe Foo: base pubblica {}; ', il tuo assegno non catturerà nulla. –

risposta

8

Rendi privato il costruttore (o distruttore) di Base, quindi T a friend. In questo modo l'unica cosa che può costruire/distruggere uno Base<T> è un T.

+0

Fare riferimento a http://stackoverflow.com/questions/702650/making-a-template-parameter-a-friend. Sembra che tu non possa essere amico di un parametro di modello. –

+0

@Hot.PxL La risposta più votata in questa domanda spiega come farlo alla fine ('friend T;', non 'friend class T;'). –

+0

Hai ragione. Ma c'è un modo per farlo dove C++ 11 non è disponibile? –

3

Se la classe contiene un codice che dice:

T* pT = 0; 
Base *pB = pT; 

Poi ci sarà un errore del compilatore se T non è l'assegnazione-compatibile con Base.

Questo tipo di controllo è formalizzata in C++ 11 in modo da non dover scrivere a mano e può ottenere utile messaggi di errore:

#include <type_traits> 

template<typename T> 
class Base { 

public: 
    void do_something() 
    { 
     static_assert(
      std::is_base_of<Base, T>::value, 
      "T must be derived from Base"); 
    } 
}; 

class B : public Base<B> { }; 

int main() 
{ 
    B b; 
    b.do_something(); 
} 

Quanto a garantire che il parametro di tipo Base s' è esattamente la classe che ne deriva, che sembra concettualmente imperfetta. Una classe che agisce come una classe base non può "parlare" del tipo che la sta ereditando. Può essere ereditato più di una volta tramite eredità multipla, o per niente.

+0

Immagino che soddisfi solo un lato dell'equazione. Voglio anche assicurarmi che 'B' sia esattamente' SomeoneElse'. –

+0

Questo errore in tutti i casi su clang3.6 si lamenta che la classe derivata è incompleta durante il static_assert. –

+0

@RichardHodges Può dipendere da dove viene inserito 'static_assert'. Ho aggiunto un esempio completo che sembra buono su MSVC 2013. Lo proveremo all'ultimo clang quando ne avrò la possibilità. –

1

Due buone risposte finora. Ecco un'altra che usa l'idioma di generare chiavi di accesso personalizzate per determinati metodi (in questo caso un costruttore). Fornisce una garanzia assoluta di uso corretto mentre non espone i metodi privati ​​nella base al derivato.

Può anche essere utilizzato per controllare l'accesso ad altri metodi nella classe base caso per caso.

template<class Derived> 
struct Base 
{ 
private: 
    // make constructor private 
    Base() = default; 
protected: 
    // This key is protected - so visible only to derived classes 
    class creation_key{ 
     // declare as friend to the derived class 
     friend Derived; 
     // make constructor private - only the Derived may create a key 
     creation_key() = default; 
    }; 

    // allow derived class to construct me with a key 
    Base(creation_key) 
    {} 

    // other methods available to the derived class go here 

private: 
    // the rest of this class is private, even to the derived class 
    // (good encapsulation) 
}; 

struct D1 : Base<D1> 
{ 
    // provide the key 
    D1() 
    : Base<D1>(creation_key()) 
    {} 

};