Warum nicht von STL-Kontainern ableiten?
-
Wo genau macht Stroustrup das?
Macht meiner Meinung nach nie Sinn. Es gibt zum Einen keine protected Member auf die man zugreifen können möchte und zum Anderen kann man das ganze nicht polymorph benutzen, da der Destruktor und auch keine Methode virtuell ist.
Vielleicht versteh ich aber auch nur deine Begründung mit dem Framework falsch.
-
@Konrad
Dies hat schon sinn, aber nur unter der Bedingung, dass man niemals über den Basisklassenzeiger darauf zugreift.Das ist ein Wrapperersatz und kann dann eingesetzt werden, wenn man nur ein oder 2 methoden hinzufügen oder ändern will, dann braucht man nicht die 20 anderen methoden weiterleiten. Stell dir das am besten nicht als polymorphe Klassenhierarchie vor, sondern als einzelne voneinander unabhängige Klassen. Die Ableitung ist nur ein implementationsdetail.In der STL implementation meines Compilers wird das zb so gemacht, da wird erst eine Klasse std::list_base erstellt, und davon dann die list abgeleitet. Da das normalerweise niemand weis, bzw nicht auf die idee kommt, nen basisklassenzeiger darauf zu erstellen, ist das okay.
Ist allerdings nur in einer generischen Umgebung sinnvoll:
template<class T> class Derived: public std::vector<T> { //operator[] const wird überschrieben const_reference operator [] (size_type n)const { return at(n); } }; template<class Vector> void foo(const Vector& vector); template<class T> void bar(const std::vector<T>& vector); //in der main Derived<int> foobar; foo(foobar);//okay bar(foobar);//autsch, impliziter basisklassencast, und die überschriebene //methode wird ignoriert, es herrscht wieder normales vector verhalten //würde jetzt der operator[] angewendet, wird kein range check mehr durchgeführt
-
Wo genau macht Stroustrup das?
Die C++ Programmiersprache, 4. Aufflage, Addison-Wesley. In §3.7.2.
Macht meiner Meinung nach nie Sinn.
Sag niemals nie

Es gibt zum Einen keine protected Member auf die man zugreifen können möchte
Nein aber public Methoden.
und zum Anderen kann man das ganze nicht polymorph benutzen, da der Destruktor und auch keine Methode virtuell ist.
Ja, kein Laufzeitpolymorphismus. Statischer Polymorphismus ist dennoch möglich.
Vielleicht versteh ich aber auch nur deine Begründung mit dem Framework falsch.

Könnte sein.
Ich nehme als Beispiel einfach die Klasse CAtlArray aus der ATL. Das ganze ist natürlich hypothetisch. CAtlArray und std::vector vertragen sich ja nicht, weil sie komplett verschieden sind.
Aber man könnte die Schnittstelle von std::vector auf CAtlArray umwrappen.template <class T, class ETraits = CElementTraits<T>, class A = std::allocator<T> > class CAtlArray : public std::vector<T, A> { /* vieles */ public: size_t GetCount() const throw() { return size(); } bool IsEmpty() const throw() { return empty(); } /* vieles */ };10000ende Zeilen von Code, die bisher unter Verwendung von CAtlArray geschrieben wurden bleiben gültig. Dazu kommt, das man jetzt CAtlArray's überall dort verwenden kann, wo std::vector gefragt ist, weil CAtlArray nur einen Zweck erfüllt, std::vector aus Kompatibilitätsgründen zu wrappen. Daher sehe ich auch das Beispiel von otze als falsch an. Der implizite Basisklassencast ist nicht wirklich schlimm oder ungewollt.
Eine Funktion, die mit std::vector arbeitet und den ungeprüften Indexoperator verwendet, tut die in der Regel zurecht. (z.B. iteration über alle Indices)
Sonst würde sie auf einem std::vector mit gleichen Inhalt undefiniertes Verhalten auslösen.
Man erkennt schon an dem Beispiel, das die ganze Sache recht weit hergeholt klingt, aber irgendein Framework leitet tatsächlich von std::string ab, weiß dazu jetzt aber nichts genaueres.MfG
DDR-RAM
-
das mit der indexprüfung war ja nur ein beispiel, aber wenn jetzt zb eine methode überschrieben wird, sodass sich ihr verhalten wirklich ändert, dann wär der basisklassencast tödlich.
-
Die Methoden werden aber nicht überschrieben, höchstens überdeckt.
Und die Funktion, die std::vector verwendet, möchte bei Aufruf von begin einen iterator erhalten, der auf den Anfang zeigt und nichts anderes! Diese Funktionalität ist perfekt in std::vector implementiert. Was soll die Wrapperklasse da noch rumwerkeln?
-
Jo, habt Recht. Habe dabei nicht an statischen Polymorphismus gedacht.

-
Warum für diese Zwecke öffentlich abgeleitet werden soll, will mir trotzdem nicht einleuchten. Private Vererbung + using tut es auch.
-
Warum private-Vererbung?
Private-Vererbung ohne polymorphismus macht für mich eher wenig sinn -> Komposition.
Außerdem: im Stroustroup-Fall soll nur eine Methode überdeckt werden, alle anderen bleiben identisch und sollen auch öffentlich sichtbar sein!
Im zweiten Frameworkkompatibilitätsfall:
Es geht ja gerade darum, dass sich die vector-klasse jetzt genauso, wie std::vector verhält und andere Methoden nur aus Kompatibilitätsgründen da sind.
Beispiel// alter Code void foo2(int& i, int j); void foo(FW::CVector<int>& rVec) { for (size_t i = 0; i < rVec.GetCount(); ++i) foo2(rVec[i], i); } // fester Code, andere externe lib void bar(std::vector<int>& rVec) { for_each(rVec.begin(), rVec.end(), bar2()); } // und ein neu geschriebenes Code schnippsel void foobar(FW::CVector<int>& rVec) { // nutze Framework-Funktionen foo(rVec); // nutze externe lib bar(rVec); // private Vererbung, wäre hier nicht gut ;) }Das ganze ist natürlich sehr Bruchstückhaft und gestellt.
-
private inheritance vs containment
Und auch interessant: http://www.parashift.com/c++-faq-lite/private-inheritance.html
-
Letztlich ist private Vererbung nur eine Form von Aggregation (solange man daraus nicht über friends öffentliche Vererbung macht) - prinzipiell kommt man immer ohne sie aus. Ihre Verwendung muss also in ihrer Zweckmäßigkeit liegen. In Fällen wie diesen bedeutet es erheblich weniger Aufwand; bei echter Aggregation müssen wir jede Funktion neu schreiben.
-
@Artchi: Was genau hat das jetzt mit dem Thema zu tun?

Was willst du damit sagen? Für mich liest sich das so, als ob das was ich sage stimmt.
Für alle die kein Englisch können:- private-Vererbung ist quasi eine Komposition mit gewissen Zusätzen:
man kann auf protected-member zugeifen (entfällt bei der stl). - Member können den Zeiger auf die abgeleitete Klasse zum Basisklassenzeiger konvertieren (bei der normalen Komposition erhält man übrigens auch sehr leicht einen Zeiger auf diese klasse),
- man kann virtual-methoden überschreiben (entfällt auch bei der stl),
- bei private-vererbung ist das durchreichen der Funktionen einfacher (weniger Zeichen im Code) als bei normaler Komposition,
- private-Vererbung bringt keinen Performance-Vorteil,
- private-Vererbung bringt mehr zusätzliche Abhängigkeiten
Was heißt das?
Wir können private-Vererbung außen vorlassen. Eine Komposition von stl-containern ist etwas sehr natürliches und muss hier auch nicht weiter erwähnt werden.
Nochmal zur Framework-sache:- FW::CVector<int>* soll in std::vector<int>* implizit konvertiert werden können,
- Über FW::CVector<int>* soll direkt auf alle std::vector<int>* Methoden zugegriffen werden können
- FW::CVector<int> erweitert oder verändert die Schnittstelle von std::vector<int>
FW::CVector<int> ist ein std::vector<int> !
Sollte nicht die komplette Schnittstelle verfügbar sein soll, die std::vector<int> bietet, dann kann man das durch private-überdeckung regeln.
Falls FW::CVector<int>* nicht implizit in std::vector<int>* konvertiert werden können soll, dann ist natürlich eine Form der Komposition zu wählen.
z.B.class CIntVectorMitZugriffsCounter : public std::vector<int> { public: int operator [](size_type n) { ++m_nCount; return std::vector<int>::operator[](n); } private: size_t m_nCount; };Richtig ist, dass das nicht sinnvoll ist, da std::vector<int> kein polymorher Typ ist. Hier ist, wie bereits erwähnt, eine Form der Komposition zu wählen.
edit:
Ich sage mal so, in der Regel ist das ableiten von STL-Containern einfach nur ein Anfängerfehler.
- private-Vererbung ist quasi eine Komposition mit gewissen Zusätzen: