C++: Erben von std::map
Ich will Erben von std::map
, aber soweit ich weiß std::map
hat keinen virtuellen Destruktor.
Ist es daher möglich, den Anruf std::map
's Destruktor explizit in meinem Destruktor, um die ordnungsgemäße Objekt-Zerstörung?
Du musst angemeldet sein, um einen Kommentar abzugeben.
Den Destruktor nicht aufgerufen, auch wenn es nicht virtuell, aber das ist nicht das Problem.
Bekommen Sie nicht definiertes Verhalten, wenn Sie versuchen, löschen Sie ein Objekt vom Typ durch einen Zeiger auf eine
std::map
.Nutzen Komposition anstelle von Vererbung
std
Behälter sind nicht gedacht, um vererbt werden, und sollten Sie nicht.Ich nehme an, Sie wollen erweitern Sie die Funktionalität von
std::map
(sagen Sie, Sie wollen zu finden, der minimale Wert), in dem Fall haben Sie zwei, viel besser, und rechtliche, Optionen:1) vorgeschlagen, die Sie verwenden können, Zusammensetzung statt:
2) Kostenlose Funktionen:
using attribute.insert;
funktionieren könnte! Auf der anderen Seite, es ist ziemlich selten, dass Sie eigentlich müssen alle Methoden, und die Umhüllung gibt die Möglichkeit zu geben, sinnvolle Namen und höher-level-Typen 🙂private
Vererbung füris-implemented-by
, undpublic
Vererbung füris-a
.using Base::method;
Gibt es ein Missverständnis: die Vererbung außerhalb des Konzeptes der reinen OOP, C++ nicht - ist nichts mehr als eine "Zusammensetzung mit einem ungenannten Mitglied, mit ein-decay-Funktion".
Abwesenheit von virtuellen Funktionen (und der Destruktor ist nicht besonderes, in diesem Sinne) macht Ihr Objekt nicht polymorph, aber wenn das, was Sie tun, ist einfach "wiederverwenden Verhalten und setzen die nativen Schnittstelle erbschaft ist genau das, was Sie gefragt.
Destruktoren müssen nicht explizit genannt, aus einander, da Ihr Ruf ist immer gefesselt von der Spezifikation.
Ausgabe
Macht Ein embedded in B mit
wird die Ausgabe genau die gleiche.
"Nicht ableiten, wenn der Destruktor nicht virtuell" ist kein C++ zwingende Konsequenz, sondern nur eine allgemein akzeptierte, nicht geschrieben (es gibt nichts in der spec darüber: neben einem UB Aufruf von delete auf einem sockel) Regel, die sich vor C++99, beim OOP durch dynamische Vererbung und virtuelle Funktionen war die einzige Programmierparadigma von C++ unterstützt.
Natürlich viele Programmierer rund um die Welt Ihre Knochen mit dieser Art der Schule (die gleichen, die lehren iostreams als primitive, bewegt sich dann auf Arrays und Zeigern, und in der letzten Lektion der Lehrer sagt: "oh ... tehre ist auch die STL hat, vector, string und andere erweiterte Funktionen") und heute, auch wenn C++ becamed multiparadigm, immer noch darauf bestehen, mit diesem reinen OOP-Regel.
In meinem Beispiel A::~A() ist nicht virtual-genau wie A::Hallo. Was bedeutet es?
Einfach: aus dem gleichen Grund ruft
A::hello
wird nicht in BerufungB::hello
AufrufA::~A()
(durch löschen) wird nicht inB::~B()
. Wenn man das akzeptieren kann -in-Programmierung Stil- die erste Behauptung gibt es keinen Grund, Sie nicht akzeptieren können das zweite. In meinem Beispiel gibt es keineA* p = new B
erhaltendelete p
da A::~A nicht virtuellen und ich weiß, was es bedeutet.Genau dem gleichen Grund, der nicht zu machen, mit dem zweiten Beispiel B,
A* p = &((new B)->a);
mit einemdelete p;
, obwohl dies der zweite Fall, der perfekt für dual das erste, sieht nicht interessant jedermann ohne ersichtlichen Grund.Das einzige problem ist "Wartung", in dem Sinne, dass -wenn yopur-code ist, betrachtet von einem OOP-Programmierer - wird es ablehnen, nicht weil es falsch ist, in sich selbst, sondern weil er erfahren hat, das zu tun.
In der Tat, die "nicht ableiten, wenn der Destruktor nicht virtuell" ist, weil die meisten Programmierer beleave, dass es zu viele Programmierer, die nicht wissen, Sie können nicht rufen Sie delete auf einen Zeiger auf eine Basis. (Sorry, wenn dies ist nicht höflich, aber nach 30+ Jahre Programmiererfahrung ich sehe keinen anderen Grund!)
Aber deine Frage ist anders:
Ruft B::~B() (durch löschen oder durch den Geltungsbereich Endung) wird immer A::~A() da A (ob eingebettet oder geerbt) ist in jedem Fall der Teil-B -.
Folgenden Luchian Kommentare: Undefiniert Verhalten angedeutet oben in seinen Kommentaren bezieht sich auf eine Löschung auf ein Zeiger-auf-ein-Objekt-Basis ohne virtuellen Destruktor.
Entsprechend der OOP-Schule, führt dies in der Regel "nicht abgeleitet, wenn kein virtueller Destruktor existiert".
Was ich habe darauf hingewiesen,, hier, ist, dass die Gründe, die Schule richtet sich auf die Tatsache, dass jedes OOP-orientierte Objekt polymorph und alles ist polymorph sein muss adressierbar Zeiger auf eine Basis zu ermöglichen, Objekt-substitution.
Durch diese Behauptung, die Schule wird ganz bewusst versucht, bei der Herstellung erlischt die Kreuzung zwischen abgeleiteten und nicht-austauschbar, so dass eine Reine OOP-Programms werden nicht erleben, dass UB.
Meine position, einfach, bekannt, dass C++ nicht gerade OOP sind, und nicht alle die C++ - Objekte OOP-orientierten Standard, und, zuzugeben, OOP ist nicht immer ein notwendiges Bedürfnis, auch bekannt, dass C++ Vererbung ist nicht immer unbedingt die Wartung OOP-substitution.
std::map ist NICHT polymorph, so dass es NICHT ersetzbar ist.
MyMap ist die gleiche: NICHT polymorph und NICHT austauschbar.
Es hat einfach wiederverwenden std::map und setzen Sie auf die gleiche std::map-Schnittstelle. Und Vererbung ist nur der Weg zu vermeiden, eine lange boilerplate von Funktionen umgeschrieben, dass nur ruft wiederverwendet werden diejenigen.
MyMap nicht virtuellen dtor als std::map nicht ein. Und dieses -für mich - ist genug zu sagen, ein C++ - Programmierer, dass diese nicht von polymorphe Objekte und müssen nicht verwendet werden, die einen in den Ort des anderen.
Ich muss zugeben, diese position ist heute nicht geteilt wird von den meisten C++ - Experten. Aber ich denke (nur meine persönliche Meinung) dies ist einfach nur wegen Ihrer Geschichte, die sich an OOP als dogma zu dienen, nicht aufgrund eines C++ benötigen.
Mir C++ ist keine Reine OOP-Sprache, und muss nicht unbedingt immer Folgen Sie dem OOP-Paradigma, in einem Kontext, wo OOP nicht befolgt wird oder erforderlich ist.
std::map*
dass tatsächlich Punkte zuMyMap
. Und wenn Sie es löschen, kann alles passieren, einschließlich einer crash.shared_ptr<std::map>
überall...Warum ?
Gibt es zwei traditionelle Gründe Erben:
Erstere keinen Sinn macht, hier als
map
keinevirtual
Methode, so dass Sie nicht ändern können, Ihr Verhalten durch die Erben; und die letztere ist eine perversion die Verwendung der Vererbung, die nur erschwert die Wartung am Ende.Ohne eine klare Vorstellung von Ihrer beabsichtigten Verwendung (Mangel an Zusammenhang in deiner Frage), ich nehme an, dass das, was Sie wirklich wollen, ist, eine Karte-wie container, mit einigen bonus-Operationen. Es gibt zwei Möglichkeiten, dies zu erreichen:
std::map
und den entsprechenden interfacestd::map
Letzteres ist einfacher, aber es ist auch mehr offen: die original-Schnittstelle
std::map
ist noch weit geöffnet; daher ist es ungeeignet für Einschränkung Operationen.Ersteres ist mehr im Schwergewicht, zweifellos, aber bietet mehr Möglichkeiten.
Es ist bis zu Ihnen zu entscheiden, welcher der beiden Ansätze besser geeignet ist.
@Matthieu M
Sie sagte
In Bezug auf die "ehemaligen":
Den
clear()
Funktion ist virtuell, und mir macht es sehr viel Sinn, für einstd::map<key,valueClass*>::clear()
werden in einer abgeleiteten Klasse überschrieben mit einem iterator, der löscht alle wies auf Instanzen der Wert-Klasse vor dem Aufruf der Basisklasseclear()
um versehentliches zu verhindern Speicherlecks, und es ist ein trick, den ich habe tatsächlich verwendet. Wie, warum würde jemand wollen, verwenden Sie eine Karte, um Zeiger auf Klassen, sowie Polymorphismus und Verweise nicht wieder belegbar bedeutet, dass die nicht verwendet werden können in einem STL-container. Sie kann stattdessen empfehlen die Verwendung einer reference_wrapper oder smart-pointer wie einshared_ptr
(C++11 features), aber wenn Sie schreiben eine Bibliothek, die Sie wollen, dass jemand nur auf eine C++98-compiler verwenden zu können, sind nicht eine option, es sei denn, du gehst zu einem Anspruch auf mit steigern, die kann auch nicht wünschenswert sein. Und wenn Sie tatsächlich wollen, dass die Karte die alleinige Verantwortung für Ihre Inhalte, dann werden Sie nicht wollen, um mit reference_wrapper oder die meisten Implementierungen von intelligenten Zeigern.Über die "letzteren":
Wenn Sie möchten, eine Karte-Zeiger, die automatisch löscht, deutete auf den Speicher, dann die Wiederverwendung von "alle" anderen Karte, Verhalten und übergeordnete klar macht eine Menge Sinn für mich, natürlich wirst du dann auch überschreiben möchten Zuordnung/copy-Konstruktoren zu Klonen, die Spitzen Objekten, wenn Sie kopieren Sie die Karte so, dass Sie nicht doppelt löschen wies auf die Instanz der
valueClass
.Aber das erfordert nur eine sehr kleine Menge von Codierung zu implementieren.
Ich auch eine geschützt
typedef std::map<key,valueClass*> baseClassMap;
als die ersten 2 Zeilen der Deklaration der abgeleiteten Klasse Karte, so dass, dass, dass ich anrufen kannbaseClassMap::clear();
in der überschriebenenclear()
- Funktion nach dem iterator-Schleife löscht alle Instanzen desvalueClass*
enthalten in der abgeleiteten Karte, die machen die Wartung einfacher, wenn der Typ vonvalueClass*
jemals ändert.Der Punkt ist, während es möglicherweise begrenzte Anwendbarkeit in gute Codierung der Praxis, ich glaube nicht, dass es fair ist zu sagen, dass es NIE eine gute Idee, Abstieg von der Karte. Aber vielleicht hast du eine bessere Idee, die ich nicht gedacht habe, etwa wie erreichen Sie das automatische Speicher-management-Effekt ohne Zugabe eine erhebliche Menge an zusätzlichen source-code (z.B. Aggregation einer
std::map
).