it-swarm.com.de

Du sollst nicht von std :: vector erben

Ok, das ist wirklich schwer zu bekennen, aber ich habe im Moment eine starke Versuchung, von std::vector zu erben. 

Ich brauche ungefähr 10 angepasste Algorithmen für vector und möchte, dass sie direkt Mitglieder des Vektors sind. Aber natürlich möchte ich auch den Rest von std::vector 's Schnittstelle haben. Nun, meine erste Idee als gesetzestreuer Bürger war, ein std::vector-Mitglied in der MyVector-Klasse zu haben. Aber dann müsste ich die gesamte Schnittstelle von std :: vector manuell erneut bereitstellen. Zu viel zum Tippen. Als nächstes dachte ich über die private Vererbung nach, so dass ich anstelle von Methoden eine Reihe von using std::vector::members im öffentlichen Bereich schreiben würde. Das ist eigentlich auch langweilig. 

Und hier bin ich, ich glaube wirklich, dass ich einfach öffentlich von std::vector erben kann, aber in der Dokumentation eine Warnung ausgeben sollte, dass diese Klasse nicht polymorph verwendet werden sollte. Ich denke, die meisten Entwickler sind kompetent genug 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, die die zusätzlichen Mitglieder eigentlich Mitglieder hätte, aber nicht die gesamte Schnittstelle von vector neu eingeben müsste? Ich bezweifle es, aber wenn Sie können, werde ich einfach glücklich sein.

Abgesehen davon, dass manche Idioten sowas schreiben können

std::vector<int>* p  = new MyVector

gibt es eine andererealistischeGefahr bei der Verwendung von MyVector? Indem ich realistisch sage, verwerfe ich Dinge wie eine Funktion, die einen Zeiger auf den Vektor nimmt ...

Nun, ich habe meinen Fall angegeben. Ich habe gesündigt. Jetzt liegt es an dir, mir zu vergeben oder nicht :)

166
Armen Tsirunyan

Tatsächlich stimmt nichts mit der öffentlichen Vererbung von std::vector. Wenn Sie das brauchen, tun Sie das einfach.

Ich würde vorschlagen, dies nur zu tun, wenn es wirklich notwendig ist. Nur wenn Sie mit kostenlosen Funktionen nicht das tun können, was Sie möchten (z. B. sollte ein Status beibehalten werden).

Das Problem ist, dass MyVector eine neue Entität ist. Dies bedeutet, dass ein neuer C++ - Entwickler wissen muss, was zum Teufel es ist, bevor er verwendet wird. Was ist der Unterschied zwischen std::vector und MyVector? Welches ist hier und da besser zu gebrauchen? Was ist, wenn ich std::vector in MyVector verschieben muss? Kann ich einfach swap() verwenden oder nicht?

Produziere keine neuen Objekte, nur um etwas besser aussehen zu lassen. Diese Entitäten (insbesondere solche Common) werden nicht im Vakuum leben. Sie werden in einer gemischten Umgebung mit ständig erhöhter Entropie leben.

144
Stas

Die gesamte STL wurde so konzipiert, dass Algorithmen und Container getrennt sind.

Dies führte zu einem Konzept verschiedener Arten von Iteratoren: Consteratoren, Iteratoren mit wahlfreiem Zugriff usw.

Daher empfehle ich Ihnen, diese Konvention zu akzeptieren und Ihre Algorithmen so zu gestalten, dass sie sich nicht für den Container interessieren, an dem sie arbeiten - und sie würden nur einen bestimmten Iterator-Typ benötigen müssen ihre Operationen durchführen.

Lassen Sie sich auch zu einigen guten Bemerkungen von Jeff Attwood umleiten.

84
Kos

Der Hauptgrund dafür, nicht öffentlich von std::vector zu erben, ist das Fehlen eines virtuellen Destruktors, der Sie effektiv an der polymorphen Verwendung von Nachkommen hindert. Insbesondere sind Sie nicht erlaubt für delete einen std::vector<T>*, der tatsächlich auf ein abgeleitetes Objekt zeigt (selbst wenn die abgeleitete Klasse keine Mitglieder hinzufügt), der Compiler kann Sie jedoch normalerweise nicht darüber warnen.

Unter diesen Bedingungen ist private Erbschaft erlaubt. Ich empfehle daher die private Vererbung und das Weiterleiten der erforderlichen Methoden vom übergeordneten Element (siehe unten).

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 Ihre Algorithmen überarbeiten, um die Art des Containers, auf dem sie arbeiten, abstrahieren zu lassen, und diese als frei gestaltete Funktionen belassen, wie die Mehrheit der Antwortenden darauf hinweist. Dies geschieht in der Regel, indem ein Algorithmus ein Iteratorpaar anstelle von Container als Argumente akzeptiert.

46
Basilevs

Wenn Sie dies in Betracht ziehen, haben Sie eindeutig die Sprachpedanten in Ihrem Büro erschlagen. Mit ihnen aus dem Weg, warum nicht einfach tun

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

Dadurch werden alle möglichen Fehler beseitigt, die durch versehentliches Aktualisieren Ihrer MyVector-Klasse entstehen könnten, und Sie können immer noch auf alle Vektoroperationen zugreifen, indem Sie ein wenig .v hinzufügen.

34
Crashworks

Was hoffen Sie zu erreichen? Nur etwas Funktionalität bereitstellen?

Die idiomatische Methode von C++ besteht darin, einige freie Funktionen zu schreiben, die die Funktionalität implementieren. Wahrscheinlichkeiten sind Sie benötigen nicht wirklich einen std :: vector, insbesondere für die Funktionalität, die Sie implementieren, was bedeutet, dass Sie tatsächlich die Wiederverwendbarkeit verlieren, indem Sie versuchen, von std :: vector zu erben.

Ich würde Ihnen dringend raten, sich die Standardbibliothek und die Kopfzeilen anzusehen und über ihre Funktionsweise zu meditieren.

18
Karl Knechtel

Ich denke, 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 kommt mit guten spezifischen Gründen, dies nicht zu tun - ich denke, Sie sollten mit Ihrem Plan weitermachen.

13
NPE

Es gibt keinen Grund, von std::vector zu erben, es sei denn, Sie möchten eine Klasse erstellen, die anders arbeitet als std::vector, da sie die verborgenen Details der std::vector-Definition auf eigene Weise behandelt oder wenn Sie ideologische Gründe für die Verwendung der Objekte dieser Klasse haben von std::vector's. Die Ersteller des Standards in C++ haben jedoch std::vector keine Schnittstelle (in Form geschützter Member) zur Verfügung gestellt, die eine solche vererbte Klasse nutzen könnte, um den Vektor auf bestimmte Weise zu verbessern. Tatsächlich hatten sie keine Möglichkeit, an einen spezifischen Aspekt zu denken, der möglicherweise eine Erweiterung oder Feinabstimmung der zusätzlichen Implementierung erforderlich machte, 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 ideologisch sein, da std::vectors nicht polymorph ist. Andernfalls besteht kein Unterschied, ob Sie die öffentliche Schnittstelle von std::vector über die öffentliche Vererbung oder über die öffentliche Mitgliedschaft offenlegen. (Angenommen, Sie müssen einen bestimmten Status in Ihrem Objekt beibehalten, damit Sie keine freien Funktionen nutzen können.) Aus einer weniger fundierten Note und aus ideologischer Sicht scheint es, dass std::vectors eine Art "einfache Idee" sind, so dass jegliche Komplexität in Form von Objekten verschiedener möglicher Klassen an ihrer Stelle ideologisch keinen Nutzen bringt.

6
Evgeniy

Ich habe kürzlich auch von std::vector 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, und zwar in einem std::vector. Mein Grund für das Erben war, dass ich ein bisschen zu faul war, Schnittstellen zu allen Methoden zu schreiben, und außerdem habe ich die Klasse über SWIG mit Python verbunden, wo std::vector bereits guter Schnittstellencode ist. Ich fand es viel einfacher, diesen Schnittstellencode auf meine Klasse zu erweitern, als einen neuen von Grund auf zu schreiben.

Das einzige Problem, das ich mit dem Ansatz sehen kann, ist nicht so sehr der nicht virtuelle Destruktor, sondern einige andere Methoden, die ich gerne überladen möchte, wie Push_back(), resize(), insert() usw. Private Vererbung könnte in der Tat eine gute Option sein .

Vielen Dank!

2
Joel Andersson

Praktisch: Wenn Sie in Ihrer abgeleiteten Klasse keine Datenelemente haben, haben Sie keine Probleme, auch nicht bei polymorphem Gebrauch. Sie benötigen einen virtuellen Destruktor nur, wenn sich die Größen der Basisklasse und der abgeleiteten Klasse unterscheiden und/oder Sie über virtuelle Funktionen verfügen (dh eine V-Tabelle).

ABER theoretisch: Von [expr.delete] in C++ 0x FCD: Bei der ersten Alternative (Objekt löschen) unterscheidet sich der statische Typ des zu löschenden Objekts von seinem dynamischen Typ, dem statischen type muss eine Basisklasse des dynamischen Typs des zu löschenden Objekts sein und der statische Typ muss einen virtuellen Destruktor haben, oder das Verhalten ist undefiniert.

Aber Sie können ohne Probleme privat von std :: vector ableiten . 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;
    ...
2
hmuelner

Wenn Sie dem guten C++ - Stil folgen, ist das Fehlen einer virtuellen Funktion nicht das Problem, sondern slicing (siehe https://stackoverflow.com/a/14461532/877329 ).

Warum ist das Fehlen virtueller Funktionen nicht das Problem? Weil eine Funktion nicht versuchen sollte, einen beliebigen Zeiger delete zu erhalten, da sie keinen Besitz besitzt. Daher sollten virtuelle Destruktoren nicht benötigt werden, wenn sie strengen Eigentumsrichtlinien folgen. Dies ist zum Beispiel immer falsch (mit oder ohne virtuellem 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 delete verwendet werden sollte, da die Factory möglicherweise ihren eigenen Heapspeicher verwendet. Der Anrufer kann ein Formular von share_ptr oder unique_ptr erhalten. Kurz gesagt, delete nichts, was Sie nicht direkt von new erhalten.

2
user877329

Ja, es ist sicher, solange Sie darauf achten, dass Sie nicht die Dinge tun, die nicht sicher sind. Ich glaube nicht, dass ich jemals jemanden gesehen habe, der einen Vektor mit neuem verwendet. In der Praxis wird es Ihnen wahrscheinlich gut gehen. Es ist jedoch nicht die übliche Redewendung in C++.

Können Sie weitere Informationen zu den Algorithmen geben?

Manchmal geht man eine Straße mit einem Entwurf hinunter und kann dann die anderen Pfade, die Sie möglicherweise eingeschlagen haben, nicht sehen. Die Tatsache, dass Sie mit 10 neuen Algorithmen vektorisieren müssen, klingelt bei mir - es gibt wirklich 10 allgemeine Zwecke Algorithmen, die ein Vektor implementieren kann, oder versuchen Sie, ein Objekt zu erstellen, das sowohl ein Allzweckvektor UND AND ist, der anwendungsspezifische Funktionen enthält?

Ich sage sicher nicht, dass Sie dies nicht tun sollten, es ist nur so, dass mit den Informationen, die Sie erhalten haben, die Alarmglocken geläutet werden, was mich auf den Gedanken bringt, dass mit Ihren Abstraktionen etwas nicht stimmt und es einen besseren Weg gibt, um das zu erreichen, was Sie tun wollen.

1
jcoder

Lassen Sie mich hier zwei weitere Möglichkeiten vorstellen, die Sie wollen. Eine Möglichkeit, std::vector einzuwickeln, ist eine Möglichkeit, zu erben, ohne dass die Benutzer die Möglichkeit haben, irgendetwas zu beschädigen:

  1. Lassen Sie mich noch eine weitere Möglichkeit zum Umschließen von std::vector hinzufügen, ohne viele Funktionsumhüllungen 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. Vererben von std :: span anstelle von std::vector und vermeiden Sie das dtor-Problem.
0
JiaHao Xu