2009-07-13 6 views
2

Bevor dies als Duplikat markiert ist, bin ich mir bewusst this Frage, aber in meinem Fall sprechen wir über Const-Container.Warum ist es nicht möglich, eine Konstante <Derived*> als Const Set <Base*> zu einer Funktion zu übergeben?

Ich habe 2 Klassen:

class Base { }; 
class Derived : public Base { }; 

und eine Funktion:

void register_objects(const std::set<Base*> &objects) {} 

ich als diese Funktion aufrufen möchten:

std::set<Derived*> objs; 
register_objects(objs); 

Der Compiler dies nicht akzeptiert. Warum nicht? Die Menge ist nicht modifizierbar, sodass keine Gefahr besteht, dass nicht abgeleitete Objekte in die Menge eingefügt werden. Wie kann ich das am besten machen?

Edit:
Ich verstehe, dass jetzt der Compiler in einer Art und Weise funktioniert, dass set<Base*> und set<Derived*> völlig unabhängig sind und daher die Funktion Signatur nicht gefunden wird. Meine Frage lautet nun: Warum funktioniert der Compiler so? Wäre es Einwände werden nicht const set<Derived*> als Derivat von const set<Base*>

Antwort

13

Der Grund, warum der Compiler das nicht akzeptiert, ist, dass der Standard es nicht sagt.

Der Grund für die Standard es nicht sagt, ist, dass der Ausschuss nicht tat, was eine Regel einzuführen, dass const MyTemplate<Derived*> zu const MyTemplate<Base*> auch eine verwandte Art ist, obwohl die nicht konstante Typen nicht verwandt sind. Und sie wollten sicher keine spezielle Regel für std :: set, da die Sprache im Allgemeinen keine besonderen Fälle für Bibliotheksklassen darstellt.

Der Grund, warum das Standardkomitee diese Typen nicht in Beziehung setzen wollte, ist, dass MyTemplate möglicherweise nicht die Semantik eines Containers hat. Bedenken Sie:

template <typename T> 
struct MyTemplate { 
    T *ptr; 
}; 

template<> 
struct MyTemplate<Derived*> { 
    int a; 
    void foo(); 
}; 

template<> 
struct MyTemplate<Base*> { 
    std::set<double> b; 
    void bar(); 
}; 

Dann was macht es sogar eine const MyTemplate<Derived*> als ein const MyTemplate<Base*> passieren bedeuten zu? Die beiden Klassen haben keine gemeinsamen Funktionen und sind nicht Layout-kompatibel. Du brauchst einen Konvertierungsoperator zwischen den beiden, oder der Compiler hat keine Ahnung, was zu tun ist, egal ob sie const sind oder nicht. Aber wie die Templates im Standard definiert sind, der Compiler hat auch ohne die Template-Spezialisierungen keine Ahnung, was zu tun ist.

std::set könnte selbst einen Konvertierungsoperator bereitstellen, aber das müsste nur eine Kopie (*) machen, die Sie selbst leicht genug machen können. Wenn es so etwas wie ein std::immutable_set gäbe, dann wäre es möglich, das so zu implementieren, dass ein std::immutable_set<Base*> aus einem std::immutable_set<Derived*> konstruiert werden könnte, indem einfach auf denselben pImpl verwiesen wird. Trotzdem würden seltsame Dinge passieren, wenn Sie nicht-virtuelle Operatoren in der abgeleiteten Klasse überladen würden - der Basiscontainer würde die Basisversion aufrufen, also könnte die Konvertierung den Satz de-orderen, wenn er einen nicht standardmäßigen Vergleicher hatte, mit dem irgendetwas gemacht wurde die Objekte selbst statt ihrer Adressen. So würde die Umwandlung mit schweren Vorbehalten kommen. Aber trotzdem gibt es kein immutable_set, und const ist nicht dasselbe wie unveränderlich.

Angenommen, Derived ist durch virtuelle oder mehrfache Vererbung mit Base verknüpft. Dann kann man nicht einfach die Adresse einer Derived als Adresse einer Base neuinterpretieren: In den meisten Implementierungen ändert die implizite Umwandlung die Adresse. Daraus folgt, dass Sie eine Struktur, die Derived* enthält, nicht einfach als eine Struktur mit Base* im Stapel konvertieren können, ohne die Struktur zu kopieren. Aber der C++ - Standard erlaubt dies tatsächlich für jede Nicht-POD-Klasse, nicht nur mit Mehrfachvererbung. Und Derived ist nicht POD, da es eine Basisklasse hat. Um diese Änderung zu std::set zu unterstützen, müssten die Grundlagen von Vererbung und Strukturlayout geändert werden. Es ist eine grundlegende Einschränkung der C++ - Sprache, dass Standardcontainer nicht auf die von Ihnen gewünschte Art und Weise neu interpretiert werden können, und mir sind keine Tricks bekannt, die sie so machen könnten, ohne die Effizienz oder Portabilität oder beides zu reduzieren. Es ist frustrierend, aber dieses Zeug ist schwierig.

Da Ihr Code einen Satz von Wert ist vorbei sowieso, könnte man nur diese Kopie machen:

std::set<Derived*> objs; 
register_objects(std::set<Base*>(objs.begin(), objs.end()); 

[Edit: Sie haben Ihr Codebeispiel geändert nicht von Wert zu übergeben. Mein Code funktioniert immer noch, und afaik ist das Beste, was Sie andere tun können, als die anrufende Code Refactoring ein std::set<Base*> in erster Linie zu verwenden.]

für std::set<Base*> einen Wrapper schreiben, die alle Elemente gewährleistet Derived*, die Art und Weise Java Generics Arbeit , ist einfacher als die Umwandlung zu arrangieren, die Sie effizient sein möchten. So könnte man so etwas wie:

template<typename T, typename U> 
struct MySetWrapper { 
    // Requirement: std::less is consistent. The default probably is, 
    // but for all we know there are specializations which aren't. 
    // User beware. 
    std::set<T> content; 
    void insert(U value) { content.insert(value); } 
    // might need a lot more methods, and for the above to return the right 
    // type, depending how else objs is used. 
}; 

MySetWrapper<Base*,Derived*> objs; 
// insert lots of values 
register_objects(objs.content); 

(*) Eigentlich denke ich, es könnte copy-on-write, was im Fall eines const Parameters in der typischen Art und Weise verwendet werden, bedeuten würde es nie tun muss Kopieren. Aber Copy-on-Write ist bei STL-Implementierungen ein wenig diskreditiert, und selbst wenn es nicht so wäre, würde ich bezweifeln, dass das Komitee solch ein schweres Implementierungsdetail verlangen würde.

0

Gut zu sehen, wie in der Frage angegeben Sie erwähnen, set<Base*> und set<Derived*> sind verschiedene Objekte. Ihre Funktion register_objects() benötigt ein Objekt set<Base*>. Daher weiß der Compiler nichts über register_objects(), das set<Derived*> dauert. Die Konstanz des Parameters ändert nichts. Die in der zitierten Frage genannten Lösungen scheinen die besten Dinge zu sein, die Sie tun können. Hängt davon ab, was Sie tun müssen ...

2

std::set<Base*> und std::set<Derived*> sind grundsätzlich zwei verschiedene Objekte. Obwohl die Klassen Base und Derived über die Vererbung verknüpft sind, sind sie bei der Instanziierungsebene des Compiler-Templates zwei verschiedene Instanzen (der Menge).

1

Erstens Es scheint ein wenig seltsam, dass man nicht durch Bezugnahme ist vorbei ...

Zweitens, wie in dem anderen Beitrag erwähnt, würden Sie besser dran, die Schaffung des als std vergangen-in eingestellt werden: : Setzen Sie < Base *> und dann eine abgeleitete Klasse für jedes Set-Mitglied neu ein.

Ihr Problem ergibt sich sicherlich daraus, dass die 2 Typen völlig unterschiedlich sind. std :: set < Abgeleitet *> ist in keiner Weise geerbt von std :: set < Base *> soweit der Compiler betroffen ist. Sie sind einfach zwei verschiedene Arten von Set ...

+0

Ich korrigierte die Referenzübergabe, das war ein Tippfehler in meiner Frage, in meinem Code habe ich das richtig gemacht. –

+0

Sie sind nicht einfach "einfach anders": es gibt sicherlich eine Beziehung zwischen den Typen, nur ist es nicht bloße Vererbung. – xtofl

+0

aber Template-Substitution funktioniert durch Ersetzen der Template-Parameter in der Klasse, nicht wahr? Die 2 Definitionen können als std :: set_base_ptr und std :: set_derived_ptr betrachtet werden, also grundsätzlich anders. Wenn es getan werden könnte, wäre ich sehr überrascht, würde aber gerne einräumen, dass ich nicht verstanden habe, wie die Template-Substitution funktioniert (was ohne Zweifel dazu beitragen würde, mein Template-Wissen zu erweitern). – Goz

2

Wenn Ihr register_objects Funktion ein Argument erhält, kann es ausdrückte/erwarten jedeBase Unterklasse dort. Das ist es, was es heißt.

Es ist eine Verletzung des Liskov-Substitutionsprinzips.

Dieses spezielle Problem wird auch als Covariance bezeichnet. In diesem Fall, wo Ihr Funktionsargument ein konstanter Container ist, könnte es gemacht werden, um zu arbeiten. Falls der Argument-Container veränderbar ist, kann er nicht funktionieren.

2

Schauen Sie zuerst hier: Is array of derived same as array of base.In Ihrem Fall ist die abgeleitete Menge ein völlig anderer Container als die Menge der Basis, und da es keine implizite Konvertierung gibt, die zwischen ihnen konvertiert werden kann, gibt der Compiler einen Fehler aus.

0

Wie Sie wissen, sind die beiden Klassen sehr ähnlich, wenn Sie die nicht konstanten Operationen entfernen. In C++ ist Vererbung jedoch eine Eigenschaft von Typen, während const nur ein Qualifikationsmerkmal über Typen ist. Das bedeutet, dass Sie nicht richtig angeben können, dass const X von const Y ableitet, auch wenn X von Y. Die weiteren

leitet, wenn X nicht von Y erben, das auch für alle cv-qualifizierte Varianten von X und Y gilt . Dies erstreckt sich auf std::set Instanziierungen. Da std::set<Foo> nicht von std::set<bar> erbt, erbt std::set<Foo> const nicht von std::set<bar> const entweder.

Verwandte Themen