2009-03-12 7 views
16

Ich habe das Gefühl, dass diese Frage schon einmal gestellt wurde, aber ich kann sie weder auf SO finden, noch finde ich etwas Nützliches auf Google. Vielleicht ist "kovariant" nicht das Wort, nach dem ich suche, aber dieses Konzept ist den kovarianten Rückgabetypen auf Funktionen sehr ähnlich, also denke ich, dass es wahrscheinlich richtig ist. Hier ist, was ich tun möchte, und es gibt mir einen Compiler-Fehler:C++ kovariante Vorlagen

class Base; 
class Derived : public Base; 

SmartPtr<Derived> d = new Derived; 
SmartPtr<Base> b = d; // compiler error 

Angenommen, diese Klassen sind voll konkretisiert ... Ich glaube, Sie bekommen die Idee. Es kann aus einem unklaren Grund keine SmartPtr<Derived> in eine SmartPtr<Base> konvertieren. Ich erinnere mich, dass das in C++ und vielen anderen Sprachen normal ist, obwohl ich mich im Moment nicht erinnern kann, warum.

Meine Wurzelfrage ist: Was ist der beste Weg, um diese Zuweisung durchzuführen? Zur Zeit ziehe ich den Zeiger aus dem SmartPtr heraus, betone ihn explizit auf den Basistyp und verpacke ihn dann in einen neuen SmartPtr des entsprechenden Typs (beachten Sie, dass dies keine Ressourcen verlustreich macht, da unsere selbstgewachsene Klasse SmartPtr eine intrusive Referenz verwendet Zählen). Das ist lang und unordentlich, besonders wenn ich dann die SmartPtr in ein anderes Objekt wickeln muss ... irgendwelche Abkürzungen?

Antwort

11

Sowohl der Kopierkonstruktor als auch der Zuweisungsoperator sollten in der Lage sein, ein SmartPtr eines anderen Typs zu verwenden und den Zeiger von einem auf den anderen zu kopieren. Wenn die Typen nicht kompatibel sind, wird sich der Compiler beschweren, und wenn sie kompatibel sind, haben Sie Ihr Problem gelöst. Etwas wie folgt aus:

template<class Type> class SmartPtr 
{ 
    .... 
    template<class OtherType> SmartPtr(const SmartPtr<OtherType> &blah) // same logic as the SmartPtr<Type> copy constructor 

    template<class OtherType> SmartPtr<Type> &operator=(const SmartPtr<OtherType> &blah) // same logic as the SmartPtr<Type> assignment operator 
}; 
+0

Kommentare ausblenden @MSN: Ich habe durch Versuch und Irrtum gelernt, dass dies nicht ausreicht, um den "normalen" Kopierkonstruktor und Zuweisungsoperator zu erfüllen. Also müssen Sie beide implementieren: SmartPtr (const SmartPtr &) UND Vorlage SmartPtr (const SmartPtr &) (gleich für op =) – mmmmmmmm

+0

Nun ja, das ist was ich meinte :) – MSN

+0

Ab C++ 11 Sie fügen auch move-constructor und move-assignment hinzu (diejenigen mit '&&'). –

3

Hängt von der Klasse SmartPtr ab. Wenn es einen Kopierkonstruktor (oder in Ihrem Fall einen Zuweisungsoperator) hat, der SmartPtr<T> nimmt, wobei T der Typ ist, mit dem er konstruiert wurde, dann wird es nicht funktionieren, weil SmartPtr<T1> nicht mit SmartPtr<T2> in Beziehung steht, selbst wenn T1 und T2 sind verwandt durch Vererbung.

Wenn jedoch SmartPtr einen Kopierkonstruktor/Zuweisungsoperator templatized hat, mit Template-Parameter TOther, die SmartPtr<TOther> akzeptiert, dann sollte es funktionieren.

+0

scheint dies brilliant ... Ich muss dies so schnell wie möglich testen, ob es funktioniert. Leider verwende ich MSVC6, das wichtige Template-Probleme hat, und IIRC wird es bei Template-Funktionen innerhalb von templatierten Klassen verbannen, selbst wenn Sie die Template-Parameter beim Aufruf explizit angeben. – rmeador

+0

Wirklich? - Ich weiß, dass ich genau das unter MSVC6 gemacht habe. – Eclipse

+0

Ich schrieb eine SmartPtr-Klasse, die dies unter MSVC6 richtig macht, also sollten Sie in Ordnung sein. –

2

Vorausgesetzt, dass Sie die Kontrolle über die SmartPtr Klasse haben, ist die Lösung, die ein Templat-Konstruktor zur Verfügung zu stellen:

template <class T> 
class SmartPtr 
{ 
    T *ptr; 
public: 

    // Note that this IS NOT a copy constructor, just another constructor that takes 
    // a similar looking class. 
    template <class O> 
    SmartPtr(const SmartPtr<O> &src) 
    { 
     ptr = src.GetPtr(); 
    } 
    // And likewise with assignment operator. 
}; 

Wenn die T und O-Typen kompatibel sind, es wird funktionieren, wenn Sie werden nicht einen Kompilierungsfehler bekommen.

+0

Es ist nicht immer so einfach; Sie müssen sicherstellen, dass die richtige Referenzanzahl beibehalten wird. Sie können das Abgeleitete Objekt nicht löschen, nachdem das letzte Ptr nicht mehr vorhanden ist, wenn immer noch Ptr für dasselbe Derived vorhanden ist. – MSalters

+0

Nun, ja, Sie müssen immer noch sicherstellen, dass Sie tatsächlich die Smart des intelligenten Zeigers implementieren. – Eclipse

15

SmartPtr<Base> und SmartPtr<Derived> sind zwei verschiedene Instanziierungen einer SmartPtr Vorlage. Diese neuen Klassen teilen nicht die Vererbung Base und Derived. Daher dein Problem.

what is the best way to perform this assignment operation?

SmartPtr<Base> b = d; 

Hat aufrufen nicht Zuweisungsoperator. Dies ruft die Kopie-ctor (die Kopie wird in den meisten Fällen elided) und ist genau so, wie wenn Sie schrieb:

SmartPtr<Base> b(d); 

für eine Kopie-Ctor bereitzustellen, die eine SmartPtr<OtherType> nimmt und umzusetzen. Gleiches gilt für den Zuweisungsoperator. Sie müssen den copy-ctor und op = schreiben, wobei Sie die Semantik von SmartPtr berücksichtigen.

+0

Ich bin mir bewusst, dass es nicht funktioniert, und ich bin mir jetzt wieder bewusst (danke), dass es wegen der fehlenden Beziehung zwischen den beiden Vorlagenklassen ist. Ich frage, wie man es so verhält, als ob sie verwandt wären, vielleicht indem man etwas clevere oder andere Tricks hinzufügt? – rmeador

+2

Viel hängt von der genauen Semantik der SmartPtr-Klasse ab, z. Hat es Eigentumsübertragung, tut es Referenzzählung etc. Sie müssen die Kopie-ctor und op = schreiben unter Berücksichtigung der Semantik von SmartPtr. – dirkgently

5

Vorlagen sind nicht kovariant, und das ist gut; vorstellen, was im folgenden Fall passieren würde:

vector<Apple*> va; 
va.push_back(new Apple); 

// Now, if templates were covariants, a vector<Apple*> could be 
// cast to a vector<Fruit*> 
vector<Fruit*> & vf = va; 
vf.push_back(new Orange); // Bam, we just added an Orange among the Apples! 

Um das zu erreichen, was Sie zu tun versuchen, muss die Intelligenter Zeiger-Klasse haben einen templatized Konstruktor, die entweder einen anderen Intelligenter Zeiger oder einen Zeiger eines anderen Typs nimmt. Sie können sich boost :: shared_ptr ansehen, was genau das tut.

template <typename T> 
class SmartPointer { 

    T * ptr; 

    public: 
    SmartPointer(T * p) : ptr(p) {} 
    SmartPointer(const SmartPointer & sp) : ptr(sp.ptr) {} 

    template <typename U> 
    SmartPointer(U * p) : ptr(p) {} 

    template <typename U> 
    SmartPointer(const SmartPointer<U> & sp) : ptr(sp.ptr) {} 

    // Do the same for operator= (even though it's not used in your example) 
}; 
+0

Sie machen einen ausgezeichneten Punkt mit Ihrem Beispiel ... Ich hatte das nicht bedacht. Im Falle eines intelligenten Zeigers glaube ich jedoch nicht, dass Sie auf dieses Problem stoßen, weil die Klassenschnittstelle im Wesentlichen die gleiche ist wie die Schnittstelle des spitzen Typs (überladen -> und *). – rmeador

+0

Ich denke, Ihr Beispiel ist ein falsches Beispiel dafür, dass es eigentlich nicht genau kovariant ist, denn Sie können etwas wie eine Orange in die Liste schieben. Es wird nur dann kovariant sein, wenn die öffentliche Schnittstelle nur _returns_ Fruit *, aber nicht _accepts_ Fruit * zurückgibt. C# stellt die Schlüsselwörter "in" und "out" bereit, um generische Typen jeweils kovariant oder kontravariant zu machen. Es erzwingt statisch, wie die Typen verwendet werden, um die von Ihnen beschriebene Situation zu vermeiden. – LostSalad

0

Ich denke, die einfachste Sache zu einem anderen SmartPtr automatischer Konvertierung zur Verfügung zu stellen ist nach folgendem:

template <class T> 
class SmartPtr 
{ 
public: 
    SmartPtr(T *ptr) { t = ptr; } 
    operator T *() const { return t; } 
    template <class Q> operator SmartPtr<Q>() const 
    { return SmartPtr<Q>(static_cast<Q *>(static_cast<T *>(* this))); } 
private: 
    T *t; 
}; 

Beachten Sie, dass diese Implementierung im Sinne robust ist, dass der Umwandlungsoperator Vorlage nicht benötigt wissen über die Semantik des Smart-Pointer, so dass Referenzzählung muss nicht repliziert werden usw.

+0

Versuchen Sie {return SmartPtr (t); } Der Compiler wird Ihnen sagen, ob ein T * ohne alle Umwandlungen zu Q * zugewiesen werden kann. Stellen Sie sicher, dass Ihre Referenzzähllogik die Referenzanzahl zwischen den Schablonentypen teilen kann. Ein int * Referenzzähler sollte dazu in der Lage sein. – jmucchiello