Du sollst nicht von std :: vector erben

189

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::vectorBenutzeroberfläche haben. Nun, meine erste Idee als gesetzestreuer Bürger war es, ein std::vectorMitglied in der MyVectorKlasse 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 :)

Armen Tsirunyan
quelle
9
Sie fragen sich also im Grunde, ob es in Ordnung ist, eine allgemeine Regel zu verletzen, weil Sie einfach zu faul sind, um die Schnittstelle des Containers erneut zu implementieren? Dann nein, ist es nicht. Sehen Sie, Sie können das Beste aus beiden Welten haben, wenn Sie diese bittere Pille schlucken und es richtig machen. Sei nicht dieser Typ. Schreiben Sie robusten Code.
Jim Brissom
7
Warum können / möchten Sie nicht die Funktionen hinzufügen, die Sie für Funktionen ohne Mitglied benötigen? Für mich wäre das in diesem Szenario am sichersten.
Simone
11
@ Jim: 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.
sbi
9
Das eigentliche Fleisch der Frage ist in dem einen Satz: "Ich möchte, dass sie direkt Mitglieder des Vektors sind". Nichts anderes in der Frage ist wirklich wichtig. Warum willst du das? Was ist das Problem, wenn diese Funktionalität nur als Nichtmitglieder bereitgestellt wird?
Jalf
8
@JoshC: "Du sollst" war schon immer häufiger als "du sollst", und es ist auch die Version, die in der King James Bibel zu finden ist (worauf die Leute im Allgemeinen anspielen, wenn sie schreiben "du sollst nicht [...] "). Was um alles in der Welt würde Sie dazu bringen, es als "Rechtschreibfehler" zu bezeichnen?
Ruakh

Antworten:

155

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 MyVectores 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 zwischen std::vectorund MyVector? Welches ist hier und da besser zu verwenden? Was passiert , wenn ich bewegen muß , std::vectorum MyVector? Darf ich nur benutzen swap()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.

Stas
quelle
7
Mein einziges Gegenargument dazu ist, dass man wirklich wissen muss, was er tut, um dies zu tun. Führen Sie beispielsweise keine zusätzlichen Datenelemente in ein MyVectorund versuchen Sie dann, diese an Funktionen zu übergeben, die std::vector&oder akzeptieren std::vector*. Wenn eine Kopierzuweisung mit std :: vector * oder std :: vector & erforderlich ist, treten Probleme beim Schneiden auf, bei denen die neuen Datenelemente von MyVectornicht 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.
stinky472
13
std::vector's Destruktor ist nicht virtual, deshalb sollten Sie nie davon erben
André Fratelli
2
Ich habe eine Klasse erstellt, die aus diesem Grund std :: vector öffentlich geerbt hat: Ich hatte alten Code mit einer Nicht-STL-Vektorklasse und wollte zu STL wechseln. Ich habe die alte Klasse als abgeleitete Klasse von std :: vector neu implementiert, sodass ich weiterhin die alten Funktionsnamen (z. B. Count () anstelle von size ()) im alten Code verwenden und neuen Code mit dem std :: vector schreiben kann Funktionen. Ich habe keine Datenelemente hinzugefügt, daher funktionierte der Destruktor von std :: vector gut für Objekte, die auf dem Heap erstellt wurden.
Graham Asher
3
@GrahamAsher Wenn Sie ein Objekt über einen Zeiger auf die Basis löschen und der Destruktor nicht virtuell ist, zeigt Ihr Programm ein undefiniertes Verhalten. Ein mögliches Ergebnis von undefiniertem Verhalten ist "es hat in meinen Tests gut funktioniert". Ein weiterer Grund ist, dass Ihre Großmutter per E-Mail Ihren Webbrowser-Verlauf erhält. Beide entsprechen dem C ++ - Standard. Es ändert sich auch von einem zum anderen mit Punktveröffentlichungen von Compilern, Betriebssystemen oder der Mondphase.
Yakk - Adam Nevraumont
2
@GrahamAsher Nein, wenn Sie ein Objekt über einen Zeiger auf die Basis ohne virtuellen Destruktor löschen, ist dies nach dem Standard ein undefiniertes Verhalten. Ich verstehe, was Sie denken, passiert; du liegst einfach falsch. "Der Basisklassen-Destruktor wird aufgerufen und funktioniert" ist ein mögliches Symptom (und das häufigste) dieses undefinierten Verhaltens, da dies der naive Maschinencode ist, den der Compiler normalerweise generiert. Dies macht es weder sicher noch eine gute Idee.
Yakk - Adam Nevraumont
92

Die 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 .

Kos
quelle
63

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 auf deleteeinstd::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.

class AdVector: private std::vector<double>
{
    typedef double T;
    typedef std::vector<double> vector;
public:
    using vector::push_back;
    using vector::operator[];
    using vector::begin;
    using vector::end;
    AdVector operator*(const AdVector & ) const;
    AdVector operator+(const AdVector & ) const;
    AdVector();
    virtual ~AdVector();
};

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.

Basilevs
quelle
IIUC, das Fehlen eines virtuellen Destruktors ist nur dann ein Problem, wenn die abgeleitete Klasse Ressourcen zuweist, die bei Zerstörung freigegeben werden müssen. (Sie würden in einem polymorphen Anwendungsfall nicht freigegeben, da ein Kontext, der unwissentlich über einen Zeiger auf die Basis das Eigentum an einem abgeleiteten Objekt übernimmt, den Basis-Destruktor nur dann aufruft, wenn es Zeit ist.) Ähnliche Probleme ergeben sich aus anderen überschriebenen Elementfunktionen Es wird davon ausgegangen, dass die Basisanrufe gültig sind. Aber gibt es ohne zusätzliche Ressourcen noch andere Gründe?
Peter - Monica
2
vectorDer zugewiesene Speicher ist nicht das Problem - schließlich vectorwürde der Destruktor durch einen Zeiger auf aufgerufen vector. Es ist nur so , dass der Standard verbietet deleteing 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 vom deleteOperanden 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.
Peter - Monica
@DavisHerring Ich denke wir sind uns da einig :-).
Peter - Monica
@DavisHerring Ah, ich verstehe, Sie beziehen sich auf meinen ersten Kommentar - dieser Kommentar enthielt einen IIUC und endete mit einer Frage. Ich habe später gesehen, dass es tatsächlich immer verboten ist. (Basilevs hatte eine allgemeine Erklärung abgegeben, "effektiv verhindert", und ich fragte mich, wie genau dies verhindert.) Also ja, wir sind uns einig: UB.
Peter - Monica
@Basilevs Das muss versehentlich gewesen sein. Fest.
ThomasMcLeod
36

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

struct MyVector
{
   std::vector<Thingy> v;  // public!
   void func1( ... ) ; // and so on
}

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.

Crashworks
quelle
Und Container und Algorithmen verfügbar machen? Siehe Kos 'Antwort oben.
Bruno Nery
19

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.

Karl Knechtel
quelle
5
Ich bin nicht überzeugt. Könnten Sie einen Teil des vorgeschlagenen Codes aktualisieren, um zu erklären, warum?
Karl Knechtel
6
@Armen: Gibt es neben der Ästhetik gute Gründe?
Snemarch
12
@Armen: Bessere Ästhetik und größere Großzügigkeit wären kostenlos front und backzu Funktionen. :) (Betrachten Sie auch das Beispiel von frei beginund endin C ++ 0x und Boost.)
UncleBens
3
Ich weiß immer noch nicht, was mit freien Funktionen los ist. Wenn Sie die "Ästhetik" der STL nicht mögen, ist C ++ möglicherweise ästhetisch der falsche Ort für Sie. Und das Hinzufügen einiger Mitgliedsfunktionen wird das Problem nicht beheben, da viele andere Algorithmen noch freie Funktionen sind.
Frank Osterfeld
17
Es ist schwierig, ein Ergebnis schwerer Operationen in einem externen Algorithmus zwischenzuspeichern. Angenommen, Sie müssen eine Summe aller Elemente im Vektor berechnen oder eine Polynomgleichung mit Vektorelementen als Koeffizienten lösen. Diese Operationen sind schwer und Faulheit wäre nützlich für sie. Sie können es jedoch nicht einführen, ohne den Container zu verpacken oder zu erben.
Basilevs
14

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.

NPE
quelle
9
Ihr erster Satz ist 100% der Zeit wahr. :)
Steve Fallows
5
Leider ist der zweite Satz nicht. Er hat nicht viel darüber nachgedacht. Der größte Teil der Frage ist irrelevant. Der einzige Teil davon, der seine Motivation zeigt, ist "Ich möchte, dass sie direkt Mitglieder des Vektors sind". Ich will. Kein Grund, warum dies wünschenswert ist. Was so klingt, als hätte er überhaupt nicht darüber nachgedacht .
Jalf
7

Es gibt keinen Grund zum Erben, es std::vectorsei denn, man möchte eine Klasse erstellen, die anders funktioniert als std::vector, weil sie die verborgenen Details der std::vectorDefinition auf ihre eigene Weise behandelt , oder es sei denn, man hat ideologische Gründe, die Objekte dieser Klasse anstelle von zu verwenden std::vectorsind diejenigen. Die Ersteller des Standards unter C ++ haben jedoch std::vectorkeine 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::vectors nicht polymorph sind und es ansonsten keinen Unterschied gibt, ob Sie die std::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, dass std::vectors 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.

Evgeniy
quelle
Gute Antwort. Willkommen bei SO!
Armen Tsirunyan
4

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:

class PointVector : private std::vector<PointType>
{
    typedef std::vector<PointType> Vector;
    ...
    using Vector::at;
    using Vector::clear;
    using Vector::iterator;
    using Vector::const_iterator;
    using Vector::begin;
    using Vector::end;
    using Vector::cbegin;
    using Vector::cend;
    using Vector::crbegin;
    using Vector::crend;
    using Vector::empty;
    using Vector::size;
    using Vector::reserve;
    using Vector::operator[];
    using Vector::assign;
    using Vector::insert;
    using Vector::erase;
    using Vector::front;
    using Vector::back;
    using Vector::push_back;
    using Vector::pop_back;
    using Vector::resize;
    ...
hmuelner
quelle
3
"Sie benötigen einen virtuellen Destruktor nur, wenn die Größen der Basisklasse und der abgeleiteten Klasse unterschiedlich sind und Sie über virtuelle Funktionen verfügen (dh eine V-Tabelle)." Diese Behauptung ist praktisch richtig, aber nicht theoretisch
Armen Tsirunyan
2
Ja, im Prinzip ist es immer noch undefiniertes Verhalten.
Jalf
Wenn Sie behaupten, dass dies ein undefiniertes Verhalten ist, würde ich gerne einen Beweis sehen (Zitat aus dem Standard).
hmuelner
8
@hmuelner: Leider sind Armen und Jalf in diesem Punkt richtig. Von [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>
Ben Voigt
1
Was lustig ist, weil ich tatsächlich dachte, dass das Verhalten von der Anwesenheit eines nicht trivialen Destruktors abhängt (insbesondere, dass POD-Klassen über einen Zeiger auf die Basis zerstört werden könnten).
Ben Voigt
3

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, deleteeinen 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):

void foo(SomeType* obj)
    {
    if(obj!=nullptr) //The function prototype only makes sense if parameter is optional
        {
        obj->doStuff();
        }
    delete obj;
    }

class SpecialSomeType:public SomeType
    {
    // whatever 
    };

int main()
    {
    SpecialSomeType obj;
    doStuff(&obj); //Will crash here. But caller does not know that
//  ...
    }

Im Gegensatz dazu funktioniert dies immer (mit oder ohne virtuellen Destruktor):

void foo(SomeType* obj)
    {
    if(obj!=nullptr) //The function prototype only makes sense if parameter is optional
        {
        obj->doStuff();
        }
    }

class SpecialSomeType:public SomeType
    {
    // whatever 
    };

int main()
    {
    SpecialSomeType obj;
    doStuff(&obj);
//  The correct destructor *will* be called here.
    }

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 eines share_ptroder erhalten unique_ptr. Kurz gesagt, nicht deletealles , was Sie nicht bekommen haben direkt aus new.

user877329
quelle
2

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.

jcoder
quelle
2

Ich habe auch von vor std::vectorkurzem 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!

Joel Andersson
quelle
10
Nach meiner Erfahrung wird der schlimmste Langzeitschaden häufig von Menschen verursacht, die etwas schlecht beratenes ausprobieren und " bisher keine Probleme damit hatten (gelesen bemerkt )".
Desillusioniert
0

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:

  1. Lassen Sie mich eine andere Art des Wrappings hinzufügen, std::vectorohne viele Funktions-Wrapper zu schreiben.

#include <utility> // For std:: forward
struct Derived: protected std::vector<T> {
    // Anything...
    using underlying_t = std::vector<T>;

    auto* get_underlying() noexcept
    {
        return static_cast<underlying_t*>(this);
    }
    auto* get_underlying() const noexcept
    {
        return static_cast<underlying_t*>(this);
    }

    template <class Ret, class ...Args>
    auto apply_to_underlying_class(Ret (*underlying_t::member_f)(Args...), Args &&...args)
    {
        return (get_underlying()->*member_f)(std::forward<Args>(args)...);
    }
};
  1. Erben von std :: span anstelle von std::vectorund vermeiden Sie das dtor-Problem.
JiaHao Xu
quelle
0

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

  std::vector<T> names; names.reserve(1000);

Wir können sagen

  template<typename C> 
  struct reserve_in : C { 
    reserve_in(std::size_t n) { this->reserve(n); }
  };

und dann haben, auch als Klassenmitglieder,

  . . .
  reserve_in<std::vector<T>> taken_names{1000};  // 1
  std::vector<T> given_names{reserve_in<std::vector<T>>{1000}}; // 2
  . . .

(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_inbesser durch eine kostenlose Funktionsvorlage bedient wird:

  template<typename C> 
  auto reserve_in(std::size_t n) { C c; c.reserve(n); return c; }

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:

  auto given_names{reserve_in<std::vector<T>>(1000)}; // 2
Nathan Myers
quelle