6

Background InfoSlicing und Operator C++

ich in Java habe Überlastung in eine Zeit lang Programmierung, und ich habe nur eingeschaltet zu C über ++ nur vor ein paar Monaten, so dass ich entschuldige mich, wenn die Antwort nur etwas Dummes, das ich vermisst habe! Nun, das ist alles gesagt, es ist Zeit für das Problem zur Hand! Ich entwickle eine grundlegende textbasierte Spielengine und bin kürzlich auf ein interessant spezifisches und unwahrscheinliches Problem gestoßen. Ich habe versucht, es in einem kleineren Maßstab im unten stehenden Programm auszuprobieren, und habe mich entschieden, dies (im Gegensatz zu meinem eigentlichen Spielecode) zu zeigen, um den Bildschirm nicht zu ersticken und das Problem weniger verschachtelt zu machen. Das unten modellierte Problem spiegelt das Problem mit meinem tatsächlichen Code wider, nur ohne die flauschigen Distraktoren.

Das Problem

Im Wesentlichen ist das Problem ein Polymorphismus. Ich möchte den Ausgabeoperator "< <" überladen, um als Anzeigefunktion zu dienen, die für jedes Objekt in der Hierarchie eindeutig ist. Das Problem ist, dass wenn ich diesen Operator aus einer Liste herausrufe, die diese Hierarchieelemente speichert, verlieren sie ihre Identität und rufen den Ausgabeoperator der Basisklasse auf. Normalerweise würde man dies lösen, indem man die Operatorüberladungen durch eine einfache Anzeigemethode ersetzt, die Anzeigemethode virtuell markiert und mit dem Happy Day fortfährt. Es macht mir nichts aus, diese Änderung am Code vorzunehmen, aber jetzt bin ich einfach nur neugierig. Gibt es eine Möglichkeit, Operatoren in einer Hierarchie zu überladen, die zu dem führt, was ich hier mache?

Der [Beispiel]-Code

#include <vector> 
#include <iostream> 
#include <string> 

using namespace std; 

class Polygon { 
    friend ostream& operator<<(ostream& os, const Polygon& p); 
public: 

private: 

}; 


class Rectangle : public Polygon { 
    friend ostream& operator<<(ostream& os, const Rectangle& r); 
public: 

private: 

}; 


class Square : public Rectangle { 
    friend ostream& operator<<(ostream& os, const Square& s); 
public: 

private: 

}; 

ostream& operator<<(ostream& os, const Polygon& p) { 
    os << "Polygon!" << endl; 
    return os; 
} 
ostream& operator<<(ostream& os, const Rectangle& r) { 
    os << "Rectangle!" << endl; 
    return os; 
} 
ostream& operator<<(ostream& os, const Square& s) { 
    os << "Square!" << endl; 
    return os; 
} 


int main() { 
    vector<Polygon*> listOfPoly; 
    listOfPoly.push_back(new Polygon()); 
    listOfPoly.push_back(new Rectangle()); 
    listOfPoly.push_back(new Square()); 

    for(Polygon* p : listOfPoly) { 
     cout << *p; 
    } 
} 

Ausgang für [Beispiel]-Code

Polygon! 
Polygon! 
Polygon! 

gewünschte Ausgabe für [Beispiel]-Code

Polygon! 
Rectangle! 
Square! 
+2

Übrigens, das ist eine gute Frage. Die meisten Neulinge stellen solche beschissenen Fragen. – Puppy

+0

als 'Operator <<' ist ein Freund (und kein Mitglied), die kurze Antwort ist nein, Sie benötigen einen internen Display-Operator, der als virtuell markiert ist – vsoftco

+0

Da Sie ein Java-Programmierer sind, wissen Sie, dass dies ein Speicherleck ist C++, wenn Sie den Speicher nicht freigeben: 'listOfPoly.push_back (new Polygon()); ...'Verwenden Sie stattdessen lieber intelligente Zeiger. – PaulMcKenzie

Antwort

5

Gibt es eine Möglichkeit, Operatoren in einer Hierarchie zu überlasten, die zu dem führt, wofür ich hier bin?

Nr

Das Problem ist, sind die Betreiber nicht in Hierarchie. Das Schlüsselwort friend hier nur weiterleiten-deklariert eine freie Funktion und gibt ihm privilegierten Zugriff auf die Klasse.Es macht es nicht zu einer Methode, also kann es nicht virtuell sein.


Beachten Sie, dass die Überladung des Operators nur syntaktischer Zucker ist. Der Ausdruck

os << shape; 

kann entweder in die freie Funktion lösen (wie Sie hier haben)

ostream& operator<< (ostream&, Polygon&); 

oder ein Mitglied des linken Operanden, wie

ostream& ostream::operator<<(Polygon&); 

(offensichtlich hier existiert der zweite Fall nicht, weil Sie std::ostream ändern müssten). Was die Syntax nicht kann aufgelöst werden, ist ein Mitglied des rechten Operanden.

So können Sie eine kostenlose Funktion Operator haben (die notwendigerweise nicht virtuell ist), oder ein Verfahren auf der linken Operanden (die könnte virtuell sein), aber keine Methode auf der rechten Operanden.

Die übliche Lösung ist eine einzige Überladung für die oberste Ebene der Hierarchie, die zu einer virtuellen Methode ausgibt. Also:

class Polygon { 
public: 
    virtual ostream& format(ostream&); 
}; 

ostream& operator<<(ostream& os, const Polygon& p) { 
    return p.format(os); 
} 

Jetzt nur Polygon::format implementieren und außer Kraft setzt es in den abgeleiteten Klassen.


Als Neben friend Verwendung trägt einen Code Geruch trotzdem. Im Allgemeinen wird es als besser angesehen, Ihrer Klasse eine öffentliche Schnittstelle zu geben, die vollständig genug ist, damit externer Code keinen privilegierten Zugriff benötigt, um damit zu arbeiten.

Weitere Digression für Hintergrundinformationen: multiple dispatch ist eine Sache, und C++ Überladungsauflösung behandelt es in Ordnung, wenn alle Argumenttypen bekannt sind statisch. Was nicht behandelt wird, ist die Ermittlung des dynamischen Typs jedes Arguments zur Laufzeit, und dann versucht, die beste Überladung zu finden (was, wenn Sie mehrere Klassenhierarchien betrachten, offensichtlich nicht-trivial ist).

Wenn Sie Ihre runtime-polymorphe Liste durch ein polymorphes Tupel zur Kompilierungszeit ersetzen und über ,, iterieren, wird Ihr ursprüngliches Überladungsschema korrekt ausgeführt.

+1

Nebenbei kann dieser Stream-Inserter eine 'Inline'' Freund'-Funktion sein. (Und es gibt keinen Grund, warum es nicht sein sollte, es sei denn, es muss überhaupt keine 'Freund'-Funktion sein.) – Deduplicator

+0

' Freunde 'sind keine Code-Gerüche. Es stimmt, dass "Freund" nicht die am häufigsten verwendete Sprachfunktion ist, aber dies ist ein Beispiel dafür, wann es gerechtfertigt ist. – Puppy

3

Der Operator ist kein virtuelles Mitglied. Dies bedeutet, dass es nicht möglich ist, in die abgeleitete Klasse zu versenden. Nur virtuelle Funktionen können dynamisch versendet werden. Eine typische Strategie in diesem Szenario besteht darin, einen normalen Operator zu erstellen, der eine virtuelle Funktion an der Schnittstelle zur Ausführung der Arbeit bereitstellt.

Übrigens ist new ein zusätzlicher Vorteil, eine ziemlich nutzlose Sprachfunktion in C++. Sie müssen intelligente Zeiger entdecken, sonst muss jede Codezeile, die Sie schreiben, nur für endlose lebenslange Probleme neu geschrieben werden.

Virtuelle Operatoren sind normalerweise eine ziemlich schlechte Idee. Dies liegt daran, dass Sie nur dynamisch unter this versenden können, aber Operatoren häufig mit dem Typ verwendet werden, der sie auf der RHS implementiert, oder als Nichtmitglieder. Nicht-Mitglied-Operator-Überladungen sind mächtiger als Mitglieder.

+0

verwenden Sie 'new' in einer intelligenten Zeigerinitialisierung;) – vsoftco

+2

Nein, Sie verwenden' make_unique' oder 'make_shared'. – Puppy

+0

Ha ha, ich nehme es zurück, tolle Antwort, richtig :) – vsoftco

1

Sie könnten eine virtuelle Display() - Funktion in der Basisklasse Rectangle hinzufügen. Jede Klasse in der Hierarchie kann die Funktion überschreiben und anders implementieren.

Sie müssen dann nur einen Operator < < definieren, der ein Polygon & als Parameter verwendet. Die Funktion selbst ruft nur die virtuelle Anzeigefunktion auf.

class Polygon 
{ 
public:  
    virtual void Display(ostream& os) const 
    { 
     os << "Polygon" << endl; 
    } 
}; 

class Rectangle : public Polygon 
{ 
public: 
    virtual void Display(ostream&os) const override 
    { 
     os << "Rectangle" << endl; 
    } 
}; 

ostream& operator<<(ostream& os, const Polygon& p) 
{ 
    p.Display(os); 
    return os; 
}