2010-02-04 18 views
28

Ho questa struttura di classi.polimorfismo Modelli C++

class Interface{ 
... 
} 

class Foo : public Interface{ 
... 
} 

template <class T> 
class Container{ 
... 
} 

e non ho questo costruttore di qualche altra classe Bar.

Bar(const Container<Interface> & bar){ 
... 
} 

Quando chiamo il costruttore in questo modo ottengo l'errore "nessuna corrispondenza".

Container<Foo> container(); 

Bar * temp = new Bar(container); 

Cosa c'è che non va? I modelli non sono polimorfici?

+0

I modelli non sono polimorfici. I modelli sono vincolati in fase di compilazione, a differenza degli oggetti polimorfici che sono vincolati in fase di esecuzione. –

+0

Domande correlate: http://stackoverflow.com/questions/1289167/template-polymorphism-not-working http://stackoverflow.com/questions/639248/c-covariant-templates –

risposta

38

penso che la terminologia esatta per quello che ti serve è "template covariance", nel senso che se B eredita da A, allora in qualche modo T<B> eredita da T<A>. Questo non è il caso in C++, né con Java e C# generics *.

C'è una buona ragione per evitare la covarianza del modello: questo semplicemente rimuoverà tutti i tipi di sicurezza nella classe template. Mi spiego con il seguente esempio:

//Assume the following class hierarchy 
class Fruit {...}; 

class Apple : public Fruit {...}; 

class Orange : public Fruit {...}; 

//Now I will use these types to instantiate a class template, namely std::vector 
int main() 
{ 
    std::vector<Apple> apple_vec; 
    apple_vec.push_back(Apple()); //no problem here 

    //If templates were covariant, the following would be legal 
    std::vector<Fruit> & fruit_vec = apple_vec; 

    //push_back would expect a Fruit, so I could pass it an Orange 
    fruit_vec.push_back(Orange()); 

    //Oh no! I just added an orange in my apple basket! 
} 

Di conseguenza, si dovrebbe considerare T<A> e T<B> tipi come completamente indipendenti, a prescindere dalla relazione tra A e B.

Così come si potrebbe risolvere il problema che si' stai di fronte? In Java e C#, è possibile utilizzare rispettivamente jolly limitati e vincoli:

//Java code 
Bar(Container<? extends Interface) {...} 

//C# code 
Bar<T>(Container<T> container) where T : Interface {...} 

La prossima C++ standard (nota come C++ 1x (ex C++ 0x)) inizialmente conteneva un ancora più potente meccanismo denominato Concepts, che avrebbe consentito agli sviluppatori di applicare requisiti sintattici e/o semantici sui parametri del modello, ma è stato purtroppo rimandato a data successiva. Tuttavia, Boost ha un Concept Check library che potrebbe interessarti.

Tuttavia, i concetti potrebbero essere un po 'eccessivo per il problema che si verifica, un utilizzo di un semplice asser statico come proposto da @gf è probabilmente la soluzione migliore.

* Aggiornamento: Dal .Net Framework 4, è possibile contrassegnare i parametri generici è covariant or contravariant.

5

No. Immagina che il parametro contenitore sia "hardcoded" nella classe che definisce (e che è in realtà come funziona). Quindi il tipo di contenitore è Container_Foo, che non è compatibile con Container_Interface.

Che cosa si potrebbe provare comunque è questa:

template<class T> 
Bar(const Container<T> & bar){ 
... 
} 

Eppure tipo diretto allentato il controllo in quel modo.

in realtà il modo STL (probabilmente più efficace e generico) sarebbe quella di fare

template<class InputIterator> 
Bar(InputIterator begin, InputIterator end){ 
... 
} 

... ma suppongo non si dispone di iteratori implementate nel contenitore.

+0

Questo è molto triste. Grazie per il consiglio. Non mi piace questa soluzione, ma temo che sia l'unica sinistra. –

+0

Si assume ragione. Non ho bisogno di loro in quel modo particolare. Francamente non so come implementarli e non ho il tempo di impararlo adesso. –

11

Qui ci sono due problemi: le costruzioni di default hanno il formato MyClass c;; con parentesi sembra una dichiarazione di funzione per il compilatore.

L'altro problema è che Container<Interface> è semplicemente un tipo differente allora Container<Foo> - si potrebbe fare la seguente invece effettivamente ottenere il polimorfismo:

Bar::Bar(const Container<Interface*>&) {} 

Container<Interface*> container; 
container.push_back(new Foo); 
Bar* temp = new Bar(container); 

O, naturalmente, si potrebbe fare Bar o suo costruttore un modello come Kornel ha mostrato.

Se si vuole realmente un certo tipo di sicurezza in fase di compilazione il polimorfismo, è possibile utilizzare Boost.TypeTraitsis_base_of o qualche equivalente:

template<class T> 
Bar::Bar(const Container<T>& c) { 
    BOOST_STATIC_ASSERT((boost::is_base_of<Interface, T>::value)); 
    // ... will give a compile time error if T doesn't 
    // inherit from Interface 
} 
+0

Grazie. Lo proverò. –

+0

+1: vieni a pensarci questo sarebbe un modo soluzione migliore. –

+0

Questo è veramente bello ed esattamente quello di cui ho bisogno. Non dovrò cambiare molto codice già implementato. Grazie ancora. –

-1

contenitore è un contenitore di Foo non oggetti di un contenitore di oggetti di interfaccia

E non può essere anche polimorfico, i puntatori alle cose possono essere, ma non gli oggetti stessi. Quanto grande sarebbe fessure nel contenitore devono essere per il contenitore se si potesse mettere nulla derivato da interfaccia in esso

è necessario

container<Interface*> 

o meglio

container<shared_ptr<Interface> > 
+1

@ pm100, shared_ptr ? Hai mai usato un shared_ptr ?? –

+1

@kornel potrebbe essere un refuso ... – msi

+0

@msiemeri - true, ma si noti che il resto della risposta presuppone che un contenitore memorizzi i valori, mentre potrebbe effettivamente archiviare Interface * –

2

È possibile creare un albero di ereditarietà per contenitori, che riflette l'albero di ereditarietà dei dati. Se avete i seguenti dati:

class Interface { 
public: 
    virtual ~Interface() 
     {} 
    virtual void print() = 0; 
}; 

class Number : public Interface { 
public: 
    Number(int value) : x(value) 
     {} 
    int get() const 
     { return x; } 
    void print() 
     { std::printf("%d\n", get()); }; 
private: 
    int x; 
}; 

class String : public Interface { 
public: 
    String(const std::string & value) : x(value) 
     {} 
    const std::string &get() const 
     { return x; } 
    void print() 
     { std::printf("%s\n", get().c_str()); } 
private: 
    std::string x; 
}; 

Si potrebbe anche avere i seguenti contenitori:

class GenericContainer { 
public: 
    GenericContainer() 
     {} 
    ~GenericContainer() 
     { v.clear(); } 

    virtual void add(Interface &obj) 
     { v.push_back(&obj); } 
    Interface &get(unsigned int i) 
     { return *v[ i ]; } 
    unsigned int size() const 
     { return v.size(); } 
private: 
    std::vector<Interface *> v; 
}; 

class NumericContainer : public GenericContainer { 
public: 
    virtual void add(Number &obj) 
     { GenericContainer::add(obj); } 
    Number &get(unsigned int i) 
     { return (Number &) GenericContainer::get(i); } 
}; 

class TextContainer : public GenericContainer { 
public: 
    virtual void add(String &obj) 
     { GenericContainer::add(obj); } 
    String &get(unsigned int i) 
     { return (String &) GenericContainer::get(i); } 
}; 

Questo non è il codice più performante; è solo per dare un'idea. L'unico problema con questo approccio è che ogni volta che aggiungi una nuova classe Data, devi anche creare un nuovo Container. A parte questo, hai il polimorfismo che "lavora di nuovo". Puoi essere specifico o generale:

void print(GenericContainer & x) 
{ 
    for(unsigned int i = 0; i < x.size(); ++i) { 
     x.get(i).print(); 
    } 
} 

void printNumbers(NumericContainer & x) 
{ 
    for(unsigned int i = 0; i < x.size(); ++i) { 
     printf("Number: "); 
     x.get(i).print(); 
    } 
} 

int main() 
{ 
    TextContainer strContainer; 
    NumericContainer numContainer; 
    Number n(345); 
    String s("Hello"); 

    numContainer.add(n); 
    strContainer.add(s); 

    print(strContainer); 
    print(numContainer); 
    printNumbers(numContainer); 
} 
2

Propongo la seguente soluzione alternativa, che utilizza una funzione di modello. Sebbene l'esempio utilizzi Qt's QList, nulla impedisce che la soluzione venga semplicemente trasposta in altri contenitori.

template <class D, class B> // D (Derived) inherits from B (Base) 
QList<B> toBaseList(QList<D> derivedList) 
{ 
    QList<B> baseList; 
    for (int i = 0; i < derivedList.size(); ++i) { 
     baseList.append(derivedList[i]); 
    } 
    return baseList; 
} 

Pro:

  • generale
  • type-safe
  • mediamente efficace se gli elementi sono puntatori o elementi (come le classi Qt implicitamente condivisi)
  • qualche altro buon mercato copy-costruibile

Contro:

  • richiede la creazione di un nuovo contenitore, al contrario di consentire il riutilizzo di quella originale
  • implica una certa memoria e processore sovraccarico sia per creare e popolare il nuovo contenitore, che dipendono pesantemente sul costo della copia -costruttore