C ++ typedef im Vergleich zu nicht übernommenen Vererbung

8

Ich habe eine Datenstruktur aus verschachtelten STL-Containern:

%Vor%

Diese Form der Daten wird nur kurz in meinem Programm verwendet, und es gibt nicht viel Verhalten, das an diese Typen neben dem einfachen Speichern ihrer Daten sinnvoll ist. Der Compiler (VC ++ 2010) beschwert sich jedoch, dass die resultierenden Namen zu lang sind.

Das Umdefinieren der Typen als Unterklassen der STL-Container ohne weitere Ausarbeitung scheint zu funktionieren:

%Vor%

In Anbetracht der Tatsache, dass die STL-Container nicht als Basisklasse verwendet werden sollen, besteht in diesem Szenario tatsächlich eine Gefahr?

    
Theran 26.11.2012, 07:21
quelle

3 Antworten

11

Es gibt eine Gefahr: Wenn Sie delete für einen Zeiger auf eine Basisklasse ohne virtual destructor aufrufen, haben Sie ein undefiniertes Verhalten. Sonst geht es dir gut.

Zumindest ist das die Theorie. In der Praxis, in der MSVC ABI oder dem Itanium ABI (gcc, Clang, icc, ...) delete auf einer Basisklasse ohne virtuellen Destruktor ( -Wdelete-non-virtual-dtor mit gcc und clang, sofern die Klasse über virtuelle Methoden verfügt) führt zu einem Problem, wenn Ihre abgeleitete Klasse nicht statische Attribute mit nicht-trivialen Destruktoren hinzufügt (z. B. std::string ).

In Ihrem speziellen Fall scheint das in Ordnung zu sein ... aber ...

... möchten Sie möglicherweise kapseln (mit Composition) und sinnvolle (Business-orientierte) Methoden verfügbar machen. Es wird nicht nur weniger gefährlich sein, es wird auch einfacher zu verstehen sein als it->second.find('x')->begin() ...

    
Matthieu M. 26.11.2012, 07:49
quelle
1

Ja, es gibt:

%Vor%

führt zu undefiniertem Verhalten.

    
Luchian Grigore 26.11.2012 07:23
quelle
1

Dies ist einer der umstrittenen Punkte von C ++ gegen "vererbungsbasierte klassische OOP".

Es gibt zwei Aspekte, die berücksichtigt werden müssen:

  • Ein typedef führt einen anderen Namen für denselben Typ ein: std::map<Solver::EnumValue, double> und SmValueProb sind -im ganzen Effekt- genau dasselbe und können abwechselnd verwendet werden.
  • Eine Klasse führt einen neuen Typ ein, der (im Prinzip) nichts mit etwas anderem zu tun hat.

Klassenbeziehungen werden durch die Art und Weise definiert, wie die Klasse "erfunden" ist, und wodurch implizite Operationen und Konvertierungen mit anderen Typen möglich sind.

Außerhalb bestimmter Programmierparadigmen (wie OOP, die mit dem Konzept der "inhritance" - und "is-a" -Beziehung assoziiert sind), Vererbung, impliziten Konstruktoren, impliziten Umwandlungen usw. machen alle ein und dasselbe: Let a type über die Schnittstelle eines anderen Typs hinweg verwendet werden und somit ein Netzwerk möglicher Operationen über verschiedene Typen definieren. Dies ist (allgemein gesprochen) "Polymorphismus".

Es gibt verschiedene Programmierparadigmen darüber, wie ein solches Netzwerk strukturiert werden sollte, wobei jeder versuchte, einen bestimmten Aspekt der Programmierung zu optimieren, wie die Repräsentation oder durch Laufzeit austauschbare Objekte (klassische OOP), die Darstellung von ersetzbaren Ersatz-Objekten (CRTP). , die Verwendung der generischen algorithmischen Funktion für verschiedene Typen (generische Programmierung), die Verwendung von "pure function", um die Zusammensetzung des Algorithmus auszudrücken (funktionale und lambda "Captures").

Alle von ihnen diktiert einige "Regeln" darüber, wie Sprache "Features" verwendet werden müssen, da C ++ Multiparadigma - nicht von seinen Funktionen allein die Anforderungen des Paradigmas zu erfüllen, etwas Schmutzigkeit offen zu lassen.

Wie Luchian sagte, wird das Erben einer std :: map keinen reinen austauschbaren OOP-Typ erzeugen, da ein delete überschrieben wurde Ein Basiszeiger weiß nicht, wie er den abgeleiteten Teil zerstören kann, da der Destruktor nicht virtuell ist.

Aber - tatsächlich - das ist nur ein spezieller Fall: auch pbase->find wird nicht Ihre eigene überschriebene Methode find nennen, da std::map::find nicht virtuell ist. (Aber das ist nicht undefiniert: es ist sehr gut definiert, um wahrscheinlich nicht das zu sein, was Sie beabsichtigen).

Die eigentliche Frage ist eine andere: Ist das "klassische OOP-Substitutionsprinzip" in Ihrem Design wichtig oder nicht? Mit anderen Worten, werden Sie Ihre Klassen UND ihre Basen gegenseitig austauschbar verwenden, mit Funktionen, die nur einen std::map* oder std::map& -Parameter verwenden und so tun, als ob diese Funktionen std :: map-Funktionen aufrufen würden, die zu Aufrufen Ihrer Methoden führen?

  • Wenn ja, ist Vererbung nicht der Weg zu gehen. Es gibt keine virtuellen Methoden in std :: map, daher wird der Laufzeitpolymorphismus nicht funktionieren.
  • Wenn nein, das ist: Sie schreiben gerade Ihre eigene Klasse und verwenden dabei sowohl std :: map als auch interface, ohne ihre Verwendung zu vertauschen (insbesondere vergeben Sie nicht eigene Klassen mit neuen und löschte sie mit Löschen auf einen Zeiger std :: map), bietet nur eine Reihe von Funktionen, die yourclass& oder yourclass* als Parameter, das ist völlig in Ordnung. Vielleicht ist es sogar besser als ein typedef, da Ihre Funktion nicht mehr mit einer std :: map verwendet werden kann, was die Funktionalitäten trennt.

Die Alternative kann "Kapselung" sein: Das heißt: Machen Sie die Karte und das explizite Mitglied Ihrer Klasse, lassen Sie die Karte als öffentliches Mitglied zugänglich machen oder zu einem privaten Mitglied mit einer Accessor-Funktion machen oder schreiben Sie sich die Kartenschnittstelle neu deine Klasse. Sie gat schließlich einen nicht verwandten Typ mit der gleichen Schnittstelle ein eigenes Verhalten. Um den Preis, um die gesamte Schnittstelle von etwas, das Hunderte von Methoden haben kann, neu zu schreiben.

HINWEIS:

Für jeden, der über die Gefahr des Fehlens von Vitaltoren nachdenkt, wird eine Notiz, die mit öffentlicher Sichtbarkeit verschleiert ist, das Problem nicht lösen:

%Vor%

ist UB genau wie

%Vor%

Das zweite Beispiel hat den gleichen Fehler wie das erste, es ist weniger verbreitet: Beide geben vor, einen Zeiger auf ein Teilobjekt zu verwenden, um auf dem ganzen Objekt zu operieren. Nichts im Teilobjekt lässt Sie wissen was das "enthaltende" ist.

    
Emilio Garavaglia 26.11.2012 08:18
quelle

Tags und Links