Ok, das ist wirklich schwer zu bekennen, aber ich habe im Moment eine starke Versuchung, von ihm zu erben std::vector
.
Ich benötige ungefähr 10 angepasste Algorithmen für den Vektor und möchte, dass sie direkt Mitglieder des Vektors sind. Aber natürlich möchte ich auch den Rest der std::vector
Benutzeroberfläche haben. Nun, meine erste Idee als gesetzestreuer Bürger war es, ein std::vector
Mitglied in der MyVector
Klasse zu haben. Aber dann müsste ich die gesamte Schnittstelle des std :: vector manuell bereitstellen. Zu viel zum Tippen. Als nächstes dachte ich über private Vererbung nach, damit ich, anstatt Methoden erneut bereitzustellen, eine Reihe von using std::vector::member
's in den öffentlichen Bereich schreiben würde . Das ist eigentlich auch langweilig.
Und hier bin ich, ich denke wirklich, dass ich einfach öffentlich erben kann std::vector
, aber in der Dokumentation eine Warnung geben, dass diese Klasse nicht polymorph verwendet werden sollte. Ich denke, die meisten Entwickler sind kompetent genug, um zu verstehen, dass dies sowieso nicht polymorph verwendet werden sollte.
Ist meine Entscheidung absolut nicht zu rechtfertigen? Wenn ja warum? Können Sie eine Alternative bereitstellen, bei der die zusätzlichen Mitglieder tatsächlich Mitglieder sind, bei der jedoch nicht die gesamte Schnittstelle des Vektors erneut eingegeben werden muss? Ich bezweifle es, aber wenn du kannst, werde ich einfach glücklich sein.
Abgesehen von der Tatsache, dass ein Idiot so etwas schreiben kann
std::vector<int>* p = new MyVector
Gibt es eine andere realistische Gefahr bei der Verwendung von MyVector? Wenn ich realistisch sage, verwerfe ich Dinge wie die Vorstellung einer Funktion, die einen Zeiger auf den Vektor nimmt ...
Nun, ich habe meinen Fall dargelegt. Ich habe gesündigt. Jetzt liegt es an dir, mir zu vergeben oder nicht :)
std::vector
's Oberfläche ist ziemlich groß, und wenn C ++ 1x kommt, wird es stark erweitert. Das ist viel zu tippen und mehr in ein paar Jahren zu erweitern. Ich denke, dies ist ein guter Grund, Vererbung statt Eindämmung in Betracht zu ziehen - wenn man der Prämisse folgt, dass diese Funktionen Mitglieder sein sollten (was ich bezweifle). Die Regel, nicht von STL-Containern abzuleiten, lautet, dass sie nicht polymorph sind. Wenn Sie sie nicht auf diese Weise verwenden, gilt dies nicht.Antworten:
Eigentlich ist an der öffentlichen Vererbung von nichts auszusetzen
std::vector
. Wenn Sie das brauchen, machen Sie das einfach.Ich würde vorschlagen, dies nur zu tun, wenn es wirklich notwendig ist. Nur wenn Sie mit freien Funktionen nicht machen können, was Sie wollen (zB sollten Sie einen Zustand beibehalten).
Das Problem ist, dass
MyVector
es sich um eine neue Entität handelt. Dies bedeutet, dass ein neuer C ++ - Entwickler wissen sollte, was zum Teufel es ist, bevor er es verwendet. Was ist der Unterschied zwischenstd::vector
undMyVector
? Welches ist hier und da besser zu verwenden? Was passiert , wenn ich bewegen muß ,std::vector
umMyVector
? Darf ich nur benutzenswap()
oder nicht?Produziere keine neuen Entitäten, nur um etwas besser aussehen zu lassen. Diese Wesenheiten (insbesondere solche, die häufig vorkommen) werden nicht im luftleeren Raum leben. Sie werden in einer gemischten Umgebung mit ständig erhöhter Entropie leben.
quelle
MyVector
und versuchen Sie dann, diese an Funktionen zu übergeben, diestd::vector&
oder akzeptierenstd::vector*
. Wenn eine Kopierzuweisung mit std :: vector * oder std :: vector & erforderlich ist, treten Probleme beim Schneiden auf, bei denen die neuen Datenelemente vonMyVector
nicht kopiert werden. Das gleiche gilt für den Aufruf von swap über einen Basiszeiger / eine Basisreferenz. Ich neige dazu zu denken, dass jede Art von Vererbungshierarchie, die das Schneiden von Objekten riskiert, schlecht ist.std::vector
's Destruktor ist nichtvirtual
, deshalb sollten Sie nie davon erbenDie gesamte STL wurde so konzipiert, dass Algorithmen und Container getrennt sind .
Dies führte zu einem Konzept verschiedener Arten von Iteratoren: konstante Iteratoren, Iteratoren mit wahlfreiem Zugriff usw.
Daher empfehle ich Ihnen, diese Konvention zu akzeptieren und Ihre Algorithmen so zu gestalten, dass sie sich nicht darum kümmern, an welchem Container sie arbeiten - und dass sie nur einen bestimmten Iteratortyp benötigen, den sie zur Ausführung benötigen Operationen.
Lassen Sie mich Sie auch auf einige gute Bemerkungen von Jeff Attwood umleiten .
quelle
Der Hauptgrund dafür, dass Sie nicht
std::vector
öffentlich erben , ist das Fehlen eines virtuellen Destruktors, der Sie effektiv daran hindert, Nachkommen polymorph zu verwenden. Insbesondere sind Sie erlaubt nicht aufdelete
einstd::vector<T>*
, dass tatsächlich Punkte auf ein abgeleitetes Objekt (auch wenn die abgeleitete Klasse keine Mitglieder hinzufügt), noch der Compiler im Allgemeinen kann man darüber nicht warnen.Private Vererbung ist unter diesen Bedingungen zulässig. Ich empfehle daher, die private Vererbung zu verwenden und die erforderlichen Methoden vom übergeordneten Element weiterzuleiten, wie unten gezeigt.
Sie sollten zunächst überlegen, Ihre Algorithmen zu überarbeiten, um den Containertyp zu abstrahieren, auf dem sie ausgeführt werden, und sie als Funktionen mit freien Vorlagen zu belassen, wie von der Mehrheit der Antwortenden hervorgehoben. Dies geschieht normalerweise, indem ein Algorithmus ein Paar Iteratoren anstelle von Containern als Argumente akzeptiert.
quelle
vector
Der zugewiesene Speicher ist nicht das Problem - schließlichvector
würde der Destruktor durch einen Zeiger auf aufgerufenvector
. Es ist nur so , dass der Standard verbietetdelete
ing Free - Store - Objekte durch eine Basisklasse Ausdruck. Der Grund ist sicherlich, dass der (De-) Zuordnungsmechanismus versuchen kann, auf die Größe des Speicherblocks zu schließen, um ihn vomdelete
Operanden zu befreien , beispielsweise wenn es mehrere Zuordnungsbereiche für Objekte bestimmter Größen gibt. Diese Einschränkung gilt afaics nicht für die normale Zerstörung von Objekten mit statischer oder automatischer Lagerdauer.Wenn Sie dies in Betracht ziehen, haben Sie die Sprachpedanten in Ihrem Büro eindeutig bereits getötet. Warum nicht einfach mit ihnen aus dem Weg
Dadurch werden alle möglichen Fehler umgangen, die durch versehentliches Upcasting Ihrer MyVector-Klasse entstehen können, und Sie können weiterhin auf alle Vektoroperationen zugreifen, indem Sie ein wenig hinzufügen
.v
.quelle
Was hoffen Sie zu erreichen? Nur einige Funktionen bereitstellen?
Die C ++ - idiomatische Möglichkeit, dies zu tun, besteht darin, nur einige freie Funktionen zu schreiben, die die Funktionalität implementieren. Möglicherweise benötigen Sie nicht wirklich einen std :: vector, insbesondere für die von Ihnen implementierte Funktionalität. Dies bedeutet, dass Sie tatsächlich die Wiederverwendbarkeit verlieren, wenn Sie versuchen, von std :: vector zu erben.
Ich würde Ihnen dringend empfehlen, sich die Standardbibliothek und die Header anzusehen und darüber nachzudenken, wie sie funktionieren.
quelle
front
undback
zu Funktionen. :) (Betrachten Sie auch das Beispiel von freibegin
undend
in C ++ 0x und Boost.)Ich denke, nur sehr wenige Regeln sollten 100% der Zeit blind befolgt werden. Es hört sich so an, als hätten Sie viel darüber nachgedacht und sind überzeugt, dass dies der richtige Weg ist. Also - es sei denn , jemand mit guten aufkommt spezifischen Gründen , dies nicht zu tun - ich denke , Sie sollten mit Ihrem Plan voran gehen.
quelle
Es gibt keinen Grund zum Erben, es
std::vector
sei denn, man möchte eine Klasse erstellen, die anders funktioniert alsstd::vector
, weil sie die verborgenen Details derstd::vector
Definition auf ihre eigene Weise behandelt , oder es sei denn, man hat ideologische Gründe, die Objekte dieser Klasse anstelle von zu verwendenstd::vector
sind diejenigen. Die Ersteller des Standards unter C ++ haben jedochstd::vector
keine Schnittstelle (in Form von geschützten Elementen) bereitgestellt, die eine solche geerbte Klasse nutzen könnte, um den Vektor auf eine bestimmte Weise zu verbessern. In der Tat hatten sie keine Möglichkeit, an irgendetwas zu denken bestimmten Aspekt vorzustellen, der möglicherweise erweitert oder eine zusätzliche Implementierung verfeinert werden musste, sodass sie nicht daran denken mussten, eine solche Schnittstelle für irgendeinen Zweck bereitzustellen.Die Gründe für die zweite Option können nur ideologischer Natur sein, da
std::vector
s nicht polymorph sind und es ansonsten keinen Unterschied gibt, ob Sie diestd::vector
öffentliche Schnittstelle über die öffentliche Vererbung oder über die öffentliche Mitgliedschaft verfügbar machen. (Angenommen, Sie müssen einen bestimmten Status in Ihrem Objekt beibehalten, damit Sie nicht mit freien Funktionen davonkommen können.) Weniger vernünftig und aus ideologischer Sicht scheint es, dassstd::vector
s eine Art "einfache Idee" ist, so dass jede Komplexität in Form von Objekten verschiedener möglicher Klassen an ihrer Stelle ideologisch keinen Nutzen hat.quelle
In der Praxis: Wenn Sie keine Datenelemente in Ihrer abgeleiteten Klasse haben, haben Sie keine Probleme, auch nicht bei der polymorphen Verwendung. Sie benötigen einen virtuellen Destruktor nur, wenn die Größen der Basisklasse und der abgeleiteten Klasse unterschiedlich sind und / oder Sie über virtuelle Funktionen verfügen (dh eine V-Tabelle).
ABER theoretisch: Von [expr.delete] in der C ++ 0x FCD: Wenn sich in der ersten Alternative (Objekt löschen) der statische Typ des zu löschenden Objekts von seinem dynamischen Typ unterscheidet, muss der statische Typ a sein Basisklasse des dynamischen Typs des zu löschenden Objekts und des statischen Typs muss einen virtuellen Destruktor haben oder das Verhalten ist undefiniert.
Aber Sie können problemlos privat von std :: vector abzuleiten. Ich habe das folgende Muster verwendet:
quelle
[expr.delete]
in der C ++ 0x-FCD: <quote> Wenn sich der statische Typ des zu löschenden Objekts von seinem dynamischen Typ unterscheidet, muss der statische Typ in der ersten Alternative (Objekt löschen) eine Basisklasse des dynamischen Typs sein des zu löschenden Objekts und der statische Typ muss einen virtuellen Destruktor haben oder das Verhalten ist undefiniert. </ quote>Wenn Sie einem guten C ++ - Stil folgen, ist das Fehlen einer virtuellen Funktion nicht das Problem, sondern das Schneiden (siehe https://stackoverflow.com/a/14461532/877329) ).
Warum ist das Fehlen virtueller Funktionen nicht das Problem? Weil eine Funktion nicht versuchen sollte,
delete
einen Zeiger zu empfangen, den sie empfängt, da sie kein Eigentum daran hat. Wenn Sie strenge Eigentumsrichtlinien befolgen, sollten Sie daher keine virtuellen Destruktoren benötigen. Dies ist beispielsweise immer falsch (mit oder ohne virtuellen Destruktor):Im Gegensatz dazu funktioniert dies immer (mit oder ohne virtuellen Destruktor):
Wenn das Objekt von einer Factory erstellt wird, sollte die Factory auch einen Zeiger auf einen funktionierenden Deleter zurückgeben, der anstelle von verwendet werden sollte
delete
, da die Factory möglicherweise einen eigenen Heap verwendet. Der Anrufer kann es in Form einesshare_ptr
oder erhaltenunique_ptr
. Kurz gesagt, nichtdelete
alles , was Sie nicht bekommen haben direkt ausnew
.quelle
Ja, es ist sicher, solange Sie darauf achten, die Dinge nicht zu tun, die nicht sicher sind ... Ich glaube, ich habe noch nie jemanden gesehen, der einen neuen Vektor verwendet, sodass es Ihnen in der Praxis wahrscheinlich gut geht. Es ist jedoch nicht die übliche Redewendung in c ++ ....
Können Sie weitere Informationen zu den Algorithmen geben?
Manchmal geht man mit einem Design eine Straße entlang und kann dann die anderen Wege, die man eingeschlagen hat, nicht sehen - die Tatsache, dass man behauptet, mit 10 neuen Algorithmen Vektoren zu läuten, läutet Alarmglocken für mich - gibt es wirklich 10 allgemeine Zwecke Algorithmen, die ein Vektor implementieren kann, oder versuchen Sie, ein Objekt zu erstellen, das sowohl ein Allzweckvektor als auch anwendungsspezifische Funktionen enthält?
Ich sage mit Sicherheit nicht, dass Sie dies nicht tun sollten. Es ist nur so, dass mit den Informationen, die Sie gegeben haben, Alarmglocken läuten, was mich denken lässt, dass möglicherweise etwas mit Ihren Abstraktionen nicht stimmt und es einen besseren Weg gibt, das zu erreichen, was Sie erreichen wollen.
quelle
Ich habe auch von vor
std::vector
kurzem geerbt und fand es sehr nützlich und bisher habe ich keine Probleme damit gehabt.Meine Klasse ist eine spärliche Matrixklasse, was bedeutet, dass ich meine Matrixelemente irgendwo speichern muss, nämlich in einer
std::vector
. Mein Grund für das Erben war, dass ich etwas zu faul war, um Schnittstellen zu allen Methoden zu schreiben, und dass ich die Klasse über SWIG mit Python verbinde, für das es bereits guten Schnittstellencode gibtstd::vector
. Ich fand es viel einfacher, diesen Schnittstellencode auf meine Klasse auszudehnen, als einen neuen von Grund auf neu zu schreiben.Das einzige Problem , das ich mit dem Ansatz sehen kann , ist nicht so sehr mit dem nicht-virtuellen Destruktor, sondern einige andere Methoden, die ich Überlastung mochte, wie
push_back()
,resize()
,insert()
usw. Privat Erbe könnten in der Tat eine gute Option sein.Vielen Dank!
quelle
Lassen Sie mich hier zwei weitere Möglichkeiten vorstellen, um zu wollen, was Sie wollen. Eine ist eine andere Art zu verpacken
std::vector
, eine andere ist die Art zu erben, ohne den Benutzern die Möglichkeit zu geben, etwas zu beschädigen:std::vector
ohne viele Funktions-Wrapper zu schreiben.std::vector
und vermeiden Sie das dtor-Problem.quelle
Diese Frage führt garantiert zu einer atemlosen Perlenkupplung, aber tatsächlich gibt es keinen vertretbaren Grund, die Ableitung von einem Standardbehälter zu vermeiden oder "unnötig zu multiplizieren", um eine Ableitung zu vermeiden. Der einfachste, kürzeste Ausdruck ist am klarsten und am besten.
Sie müssen bei jedem abgeleiteten Typ die übliche Sorgfalt walten lassen, aber der Fall einer Basis aus dem Standard ist nichts Besonderes. Das Überschreiben einer Basismitgliedsfunktion könnte schwierig sein, aber das wäre mit einer nicht virtuellen Basis unklug, daher gibt es hier nicht viel Besonderes. Wenn Sie ein Datenelement hinzufügen würden, müssten Sie sich Gedanken über das Schneiden machen, wenn das Mitglied mit dem Inhalt der Basis konsistent gehalten werden müsste, aber auch dies gilt für jede Basis.
Der Ort, an dem ich festgestellt habe, dass das Ableiten von einem Standardcontainer besonders nützlich ist, besteht darin, einen einzelnen Konstruktor hinzuzufügen, der genau die erforderliche Initialisierung ausführt, ohne dass die Gefahr einer Verwechslung oder Entführung durch andere Konstruktoren besteht. (Ich sehe Sie an, Initialisierungslisten-Konstruktoren!) Dann können Sie das resultierende Objekt frei verwenden, in Scheiben geschnitten - übergeben Sie es unter Bezugnahme auf etwas, das die Basis erwartet, und verschieben Sie es von ihm zu einer Instanz der Basis, was haben Sie. Es gibt keine Randfälle, über die Sie sich Sorgen machen müssen, es sei denn, Sie würden ein Vorlagenargument an die abgeleitete Klasse binden.
Ein Ort, an dem diese Technik in C ++ 20 sofort nützlich sein wird, ist die Reservierung. Wo wir vielleicht geschrieben haben
Wir können sagen
und dann haben, auch als Klassenmitglieder,
(je nach Präferenz) und müssen keinen Konstruktor schreiben, nur um Reserve () aufzurufen.
(Der Grund dass
reserve_in
technisch auf C ++ 20 gewartet werden muss, ist, dass frühere Standards nicht erfordern, dass die Kapazität eines leeren Vektors über Bewegungen hinweg erhalten bleibt. Dies wird als Versehen anerkannt und kann vernünftigerweise als behoben angesehen werden Wir können auch davon ausgehen, dass die Korrektur effektiv auf frühere Standards zurückgesetzt wird, da alle vorhandenen Implementierungen tatsächlich die Kapazität über Bewegungen hinweg erhalten, die Standards haben sie einfach nicht benötigt. Der Eifrige kann sicher springen Die Waffenreservierung ist sowieso fast immer nur eine Optimierung.)Einige würden argumentieren, dass der Fall von
reserve_in
besser durch eine kostenlose Funktionsvorlage bedient wird:Eine solche Alternative ist sicherlich realisierbar - und könnte aufgrund von * RVO manchmal sogar unendlich schneller sein. Die Wahl der Ableitung oder der freien Funktion sollte jedoch von sich aus getroffen werden und nicht aus unbegründetem (heh!) Aberglauben über die Ableitung von Standardkomponenten. In der obigen Beispielverwendung würde nur die zweite Form mit der freien Funktion funktionieren. obwohl es außerhalb des Klassenkontexts etwas präziser geschrieben werden könnte:
quelle