2009-03-06 19 views
7

Qual è la relazione tra l'utilizzo di funzioni virtuali e meccanismi di ereditarietà C++ rispetto all'utilizzo di modelli e qualcosa come concetti di incremento?Controllo del concetto C++ rispetto all'ereditarietà

Sembra che ci sia una certa sovrapposizione di ciò che è possibile. Vale a dire, sembra possibile ottenere un comportamento polimorfico con entrambi gli approcci. Quindi, quando ha senso favorire l'una rispetto all'altra?

Il motivo per cui viene presentato è perché ho un contenitore basato su modelli, in cui i contenitori stessi hanno una relazione gerarchica. Mi piacerebbe scrivere algoritmi che usano questi contenitori senza preoccuparsi di quale contenitore specifico sia. Inoltre, alcuni algoritmi trarrebbero vantaggio dal sapere che il tipo di modello soddisfaceva determinati concetti (confrontabile, ad esempio).

Quindi, da un lato, voglio che i contenitori si comportino in modo polimorfico. Dall'altro, devo ancora usare i concetti se voglio implementare correttamente alcuni algoritmi. Cosa deve fare uno sviluppatore junior?

risposta

6

Penso ai concetti come a una sorta di meta-interfaccia. Classificano i tipi dopo le loro abilità. La prossima versione di C++ fornisce concetti nativi. Non l'avevo capito fino a quando non mi sono imbattuto in concetti di C++ 1x e in che modo hanno permesso di mettere insieme tipi diversi ma non correlati. Immagina di avere un'interfaccia Range. Puoi modellarlo in due modi. Uno è un rapporto sottotipo:

class Range { 
    virtual Iterator * begin() = 0; 
    virtual Iterator * end() = 0; 

    virtual size_t size() = 0; 
}; 

Naturalmente, ogni classe che deriva da che implementa l'interfaccia Range e può essere utilizzato con le funzioni. Ma ora vedi che è limitato. Che dire di un array? È anche una gamma!

T t[N]; 

begin() => t 
end() => t + size() 
size() => N 

Purtroppo non è possibile ricavare una matrice dalla classe Range che implementa tale interfaccia.È necessario un metodo aggiuntivo (sovraccarico). E i contenitori di terze parti? Un utente della tua libreria potrebbe voler utilizzare i suoi contenitori insieme alle tue funzioni. Ma non può cambiare la definizione dei loro contenitori. Qui, i concetti entrano in gioco:

auto concept Range<typename T> { 
    typename iterator; 
    iterator T::begin(); 
    iterator T::end(); 
    size_t T::size(); 
} 

Ora, dire qualcosa sulle operazioni supportate di un certo tipo che possono essere soddisfatte se T ha le funzioni membro appropriate. Nella tua libreria, scriverebbe la funzione generica. Questo ti permette di accettare qualsiasi tipo purché supporta le operazioni necessarie:

template<Range R> 
void assign(R const& r) { 
    ... iterate from r.begin() to r.end(). 
} 

E 'una sorta di grande sostituibilità. Qualsiasi tipo si adatta al disegno di legge che aderisce al concetto e non solo a quei tipi che implementano attivamente un'interfaccia. Il prossimo standard C++ va oltre: definisce un concetto di Container che può essere adattato da array semplici (da qualcosa che si trova mappa concettuale che definisce come alcuni tipi si adattano ad alcuni concetti) e altri, contenitori standard esistenti.

Il motivo per cui viene presentato è perché ho un contenitore basato su modelli, in cui i contenitori stessi hanno una relazione gerarchica. Mi piacerebbe scrivere algoritmi che usano questi contenitori senza preoccuparsi di quale contenitore specifico sia. Inoltre, alcuni algoritmi trarrebbero vantaggio dal sapere che il tipo di modello soddisfaceva determinati concetti (confrontabile, ad esempio).

Si può effettivamente fare entrambi con i modelli. È possibile mantenere la relazione gerarchica per condividere il codice e quindi scrivere gli algoritmi in modo generico. Ad esempio, per comunicare che il tuo contenitore è paragonabile. E 'come standard di accesso casuale/avanti/uscita/ingresso categorie di iteratori sono implementate:

// tag types for the comparator cagetory 
struct not_comparable { }; 
struct basic_comparable : not_comparable { }; 

template<typename T> 
class MyVector : public BasicContainer<T> { 
    typedef basic_comparable comparator_kind; 
}; 

/* Container concept */ 
T::comparator_kind: comparator category 

E' un modo semplice ragionevole per farlo, in realtà. Ora puoi chiamare una funzione e inoltrerà alla corretta implementazione.

template<typename Container> 
void takesAdvantage(Container const& c) { 
    takesAdvantageOfCompare(c, typename Container::comparator_kind()); 
} 

// implementation for basic_comparable containers 
template<typename Container> 
void takesAdvantage(Container const& c, basic_comparable) { 
    ... 
} 

// implementation for not_comparable containers 
template<typename Container> 
void takesAdvantage(Container const& c, not_comparable) { 
    ... 
} 

Ci sono in realtà diverse tecniche che possono essere utilizzate per implementarlo. Un altro modo è utilizzare boost::enable_if per abilitare o disabilitare le diverse implementazioni ogni volta.

+0

C++ 1x? Significa che hanno rinunciato a rilasciare il nuovo standard in questo decennio o stai parlando del futuro sviluppo del C++? – jpalecek

+0

http://www.research.att.com/~bs/C++0xFAQ.html#concepts – jmucchiello

+0

jpalecek, vogliono rilasciarlo nel 2010. Ho l'abitudine di chiamarlo C++ 1x :) –

0

Se una decisione può essere presa in fase di compilazione, utilizzare i modelli. Altrimenti usa l'ereditarietà e le funzioni virtuali.

1

Sì, il comportamento polimorfico è possibile con entrambi i meccanismi. Infatti, entrambi sono chiamati anche polimorfismo.

Le funzioni virtuali forniscono un polimorfismo dinamico (poiché è deciso in fase di esecuzione), mentre i modelli forniscono polimorfismo statico (tutto è deciso in fase di compilazione).

E che dovrebbe rispondere alla domanda di quale preferire pure. Quando possibile, preferisci spostare il lavoro in fase di compilazione. Quindi, quando puoi farla franca, usa i modelli per risolvere i tuoi bisogni di polimorfismo. E quando ciò non è possibile (perché è necessario utilizzare le informazioni sul tipo di runtime, poiché i tipi esatti non sono noti in fase di compilazione), ricorrere al polimorfismo dinamico.

(Naturalmente ci possono essere altri motivi per preferire l'uno o l'altro.In particolare, i modelli richiedono di spostare molto codice per i file di intestazione che possono o meno essere un problema, e la velocità di compilazione tende a soffrire, che pure può o non può essere un problema.)

0

In questo caso specifico si può fare qualcosa di simile a

template<typename T> 
class ContainerBase{}; 

template<typename T> 
class ContainerDerived : public ContainerBase<T> {}; 

Dal momento che ogni tipo di 'contenitore' è unico per ogni tipo di modello, non ci sono funzioni membro ragione di ciascun tipo di contenitore non può essere specializzato sui tratti del tipo di codice.

0

Come semplice esempio della differenza tra tempo di compilazione e run-time polimorfismo consideri il seguente codice:

template<typename tType> 
struct compileTimePolymorphism 
{ }; 

// compile time polymorphism, 
// you can describe a behavior on some object type 
// through the template, but you cannot interchange 
// the templates 
compileTimePolymorphism<int> l_intTemplate; 
compileTimePolymorphism<float> l_floatTemplate; 
compileTimePolymorphism *l_templatePointer; // ???? impossible 

struct A {}; 
struct B : public A{}; 
struct C : public A{}; 

// runtime polymorphism 
// you can interchange objects of different type 
// by treating them like the parent 
B l_B; 
C l_C: 
A *l_A = &l_B; 
l_A = &l_C; 

tempo di compilazione polimorfismo è una buona soluzione quando il comportamento di un oggetto dipende da qualche altro oggetto. Il polimorfismo run-time è necessario quando è necessario modificare il comportamento di un oggetto.

I due possono essere combinati attraverso la definizione di un modello che è polimorfa:

template<typename tType> 
struct myContainer : public tType 
{}; 

La domanda allora è dove il comportamento del vostro contenitore ha bisogno di cambiare (il polimorfismo runtime), e in cui il comportamento dipende dagli oggetti contiene (polimorfismo del tempo di compilazione).