C ++ Doppelter Versand für Equals ()

8

Stellen Sie sich vor, ich habe eine abstrakte Basisklasse Shape mit den abgeleiteten Klassen Circle und Rectangle .

%Vor%

Ich muss feststellen, ob zwei Formen gleich sind, vorausgesetzt, ich habe zwei Shape* Zeiger. (Dies liegt daran, dass ich zwei Instanzen von vector<Shape*> habe und möchte sehen, ob sie die gleichen Formen haben.)

Die empfohlene Vorgehensweise hierfür ist doppelter Versand . Was ich herausgefunden habe, ist dies (hier stark vereinfacht, so dass die Formen allen anderen Formen des gleichen Typs gleich sind):

%Vor%

Das funktioniert, aber ich muss eine separate equals -Funktion und friend -Deklaration in Shape für jede abgeleitete Klasse hinzufügen. Dann muss ich die exakt gleiche equals Funktion auch in jede abgeleitete Klasse kopieren. Dies ist eine Menge Boilerplate für etwa 10 verschiedene Formen!

Gibt es einen einfacheren Weg?

dynamic_cast kommt nicht in Frage; zu langsam. (Ja, ich habe es bewertet. Geschwindigkeit zählt in meiner App.)

Ich habe das versucht, aber es funktioniert nicht:

%Vor%

equals() gibt immer false zurück, sogar bei identischen Formen. Es scheint, dass der Versand immer die Basisfunktion is_equal(Shape&) wählt, selbst wenn eine "spezifischere" Übereinstimmung verfügbar ist. Dies macht wahrscheinlich Sinn, aber ich verstehe C ++ Versand nicht gut genug, um zu wissen, warum.

    
Adam Ernst 12.09.2011, 20:16
quelle

6 Antworten

5

Wenn Sie Methoden wie folgt erstellen:

%Vor%

Und in der Unterklasse

%Vor%

Dies sind nicht die gleichen Methoden. Sie haben zwei separate virtuelle Methoden, von denen keine überschrieben (sie sind überlastet nicht einmal überlastet, wie Ben Voigt darauf hinwies). Wenn Sie Shape::is_equal aufrufen, gibt es nur eine Version: Shape::is_equal(Shape&) ..., die nicht überschrieben wird und immer false zurückgibt.

Sie müssten die separaten überladenen Methoden in der übergeordneten Klasse definieren und sie dann in der untergeordneten Klasse überschreiben. Zum Beispiel

%Vor%

Mit solchen Tricks werden Sie jedoch wahrscheinlich nicht die Leistung oder Einfachheit erreichen, mit der ein C-Programmierer es tun würde:

%Vor%

Wenn Überladung und virtuelle Methoden Ihren Code komplizierter machen als die C-Version, dann sollten Sie vielleicht überdenken, ob Sie dieses spezielle Problem mit Überladung und virtuellen Methoden lösen.

    
Dietrich Epp 12.09.2011, 20:24
quelle
5

Der Doppelversand wurde gut untersucht. Die Verallgemeinerung des Doppelversands wird als "Multi-Methode" bezeichnet.

Kapitel 11 von Modern C ++ Design behebt dieses Problem im Detail. Der von Ihnen beschriebene Ansatz mit dynamic_cast<> befindet sich in Abschnitt 11.3 "Double Switch-on-Type: Brute Force". Der Autor beschreibt sogar, wie man den Großteil der Arbeit automatisiert und automatisch die symmetrischen Überladungen erzeugt. Dann führt der Autor einen logarithmischen Versand basierend auf std::map<> und std::type_info ein. Schließlich endet der Abschnitt mit "Constant-Time Multimethods: Raw Speed", das (grob) auf einer Matrix von Callback-Funktionen basiert.

Die vorgestellte Lösung enthält ausführliche Erklärungen zur Handhabung von Funktoren und Modellen, um bei der Mehrfachvererbung (und virtuellen Vererbung) unangenehme Fallstricke zu vermeiden.

Wenn Sie in C ++ mehrere Methoden implementieren, empfehle ich Ihnen, das Buch zu lesen und die vorgeschlagene Lösung zu implementieren.

    
André Caron 12.09.2011 21:53
quelle
1

Sie könnten eine Typenzählung und ein statisches Casting verwenden, wenn dynamic_cast zu langsam ist ...

%Vor%     
Node 12.09.2011 20:32
quelle
0

Virtuelle Funktionen können einfach dynamic_cast RTTI Typ-Überprüfung ersetzen, wie folgt: Ссылка

%Vor%

-

Warum gibt es 10 Kopien der "genau gleichen" Funktion? Sollte nicht Rectangle::is_equal(const Rectangle&) const Rechteck-spezifische Mitglieder vergleichen?

Wenn alle Rechtecke in eine einzelne Äquivalenzklasse fallen, wie es bei dem von Ihnen gezeigten Code der Fall ist, können Sie nur eine einzige virtuelle Funktion haben, die die Äquivalenzklasse zurückgibt.

    
Ben Voigt 12.09.2011 21:16
quelle
0

In meinen Entwürfen verschiebe ich die Methode Shape::operator== auf private und implementiere sie nicht. Die Menge an Arbeit, um dies richtig zu lösen, ist die Mühe nicht wert.

Mit anderen Worten, mit einem Vektor von Shape * :

%Vor%

Ich kann Folgendes tun:

%Vor%

Das Problem tritt beim Vergleichen von Objekten auf:

%Vor%

Der Ausdruck entspricht:

p_shape_1- & gt; operator == (* p_shape_2);

Wenn eine virtuelle oder polymorphe Operation vorhanden ist, wird dies:

Rectangle :: operator == ( (Kreis ));

Mit anderen Worten, es besteht eine große Wahrscheinlichkeit, dass sich Rectangle mit einem Kreis oder einer anderen Form vergleicht; ein ungültiger Vergleich.

In meinen Entwürfen verbiete ich also Gleichheitsvergleiche basierend auf Basisklassenzeigern. Das einzige Zeug, das mit Zeigern zu Basisklassen verglichen werden kann, ist der Inhalt in der Basisklasse.

    
Thomas Matthews 13.09.2011 00:54
quelle
0

Ich beziehe mich normalerweise auf dynamic_cast und virtuelle Funktionen. Wenn der Compiler nicht zu dumm ist, unterscheidet sich das dynamische Umwandeln eines Schrittes nicht wesentlich davon, dass zwei Sprünge in einer VTable ausgeführt werden.

%Vor%

Gleiches gilt für das Rechteck oder eine andere Form. im Grunde erfordert die doppelte Versendung - wenn N die Klassen sind, N 2 Funktionen. Auf diese Weise brauchen Sie nur N Funktionen (eine pro Klasse).

Wenn Sie der Ansicht sind, dass die dynamische Darstellung zu langsam ist, können Sie eine in der Basisklasse deklarierte Aufzählung verwenden. und von den abgeleiteten Klassen richtig initialisiert werden. Dies erfordert jedoch, dass Sie die Enum-Werte jedes Mal aktualisieren, wenn eine neue Klasse hinzugefügt wird. Beispielsweise:     Klassenform     {     geschützt:        enum shapes_type {no_shape, circle_shape, rectangle_shape};        shapes_type mein_type;        virtual bool ist gleich (const shape * s) const = 0;        Freund bool oeprator == (const shape & amp; a, kosten shape & amp; b)        {return a.is_equal (& amp; b); }        shape (): mein_typ (no_shape)        {}     };

%Vor%

Falls die Verwendung einer base_defined enum nicht akzeptabel ist (nicht bekannte Anzahl möglicher Klassen), können Sie sich auf einen einfachen Wert verlassen (z. B. eine ganze Zahl), der eindeutig einen Typ mit einem Trick darstellen kann:

%Vor%     
Emilio Garavaglia 12.09.2011 21:09
quelle