Fast jede C ++ - Ressource, die ich gesehen habe und die solche Dinge behandelt, sagt mir, dass ich polymorphe Ansätze der Verwendung von RTTI (Runtime Type Identification) vorziehen sollte. Im Allgemeinen nehme ich diese Art von Rat ernst und werde versuchen, die Gründe zu verstehen - schließlich ist C ++ ein mächtiges Tier und in seiner ganzen Tiefe schwer zu verstehen. Für diese spezielle Frage zeichne ich jedoch eine Lücke und möchte sehen, welche Art von Rat das Internet bieten kann. Lassen Sie mich zunächst zusammenfassen, was ich bisher gelernt habe, indem ich die häufigsten Gründe aufführe, aus denen RTTI als "schädlich" eingestuft wird:
Einige Compiler verwenden es nicht / RTTI ist nicht immer aktiviert
Ich kaufe dieses Argument wirklich nicht. Es ist so, als würde ich sagen, ich sollte keine C ++ 14-Funktionen verwenden, da es Compiler gibt, die dies nicht unterstützen. Und doch würde mich niemand davon abhalten, C ++ 14-Funktionen zu verwenden. Die meisten Projekte haben Einfluss auf den verwendeten Compiler und dessen Konfiguration. Sogar die gcc-Manpage zitieren:
-fno-rtti
Deaktivieren Sie die Generierung von Informationen zu jeder Klasse mit virtuellen Funktionen zur Verwendung durch die C ++ - Laufzeit-Typidentifizierungsfunktionen (dynamic_cast und typeid). Wenn Sie diese Teile der Sprache nicht verwenden, können Sie mit diesem Flag Platz sparen. Beachten Sie, dass die Ausnahmebehandlung dieselben Informationen verwendet, diese jedoch von G ++ nach Bedarf generiert werden. Der Operator dynamic_cast kann weiterhin für Casts verwendet werden, für die keine Laufzeitinformationen erforderlich sind, dh für Casts in "void *" oder in eindeutige Basisklassen.
Dies sagt mir, dass ich es deaktivieren kann , wenn ich RTTI nicht verwende. Das heißt, wenn Sie Boost nicht verwenden, müssen Sie keine Verknüpfung herstellen. Ich muss nicht für den Fall planen, mit dem jemand kompiliert -fno-rtti
. Außerdem fällt der Compiler in diesem Fall laut und deutlich aus.
Es kostet zusätzlichen Speicher / Kann langsam sein
Wenn ich versucht bin, RTTI zu verwenden, bedeutet dies, dass ich auf eine Art von Typinformation oder Merkmal meiner Klasse zugreifen muss. Wenn ich eine Lösung implementiere, die kein RTTI verwendet, bedeutet dies normalerweise, dass ich meinen Klassen einige Felder hinzufügen muss, um diese Informationen zu speichern, sodass das Speicherargument irgendwie ungültig ist (ich werde weiter unten ein Beispiel dafür geben).
Ein dynamic_cast kann tatsächlich langsam sein. Es gibt jedoch normalerweise Möglichkeiten, um zu vermeiden, dass geschwindigkeitskritische Situationen verwendet werden müssen. Und ich sehe die Alternative nicht ganz. Diese SO-Antwort schlägt vor, eine in der Basisklasse definierte Aufzählung zum Speichern des Typs zu verwenden. Das funktioniert nur, wenn Sie alle abgeleiteten Klassen a priori kennen. Das ist ein ziemlich großes "Wenn"!
Aus dieser Antwort geht auch hervor, dass die Kosten für RTTI ebenfalls nicht klar sind. Unterschiedliche Menschen messen unterschiedliche Dinge.
Elegante polymorphe Designs machen RTTI unnötig
Dies ist die Art von Rat, die ich ernst nehme. In diesem Fall kann ich einfach keine guten Nicht-RTTI-Lösungen finden, die meinen RTTI-Anwendungsfall abdecken. Lassen Sie mich ein Beispiel geben:
Angenommen, ich schreibe eine Bibliothek, um Diagramme von Objekten zu verarbeiten. Ich möchte Benutzern erlauben, ihre eigenen Typen zu generieren, wenn sie meine Bibliothek verwenden (daher ist die Aufzählungsmethode nicht verfügbar). Ich habe eine Basisklasse für meinen Knoten:
class node_base
{
public:
node_base();
virtual ~node_base();
std::vector< std::shared_ptr<node_base> > get_adjacent_nodes();
};
Jetzt können meine Knoten von verschiedenen Typen sein. Wie wäre es mit diesen:
class red_node : virtual public node_base
{
public:
red_node();
virtual ~red_node();
void get_redness();
};
class yellow_node : virtual public node_base
{
public:
yellow_node();
virtual ~yellow_node();
void set_yellowness(int);
};
Verdammt, warum nicht auch nur eines davon:
class orange_node : public red_node, public yellow_node
{
public:
orange_node();
virtual ~orange_node();
void poke();
void poke_adjacent_oranges();
};
Die letzte Funktion ist interessant. Hier ist eine Möglichkeit, es zu schreiben:
void orange_node::poke_adjacent_oranges()
{
auto adj_nodes = get_adjacent_nodes();
foreach(auto node, adj_nodes) {
// In this case, typeid() and static_cast might be faster
std::shared_ptr<orange_node> o_node = dynamic_cast<orange_node>(node);
if (o_node) {
o_node->poke();
}
}
}
Das alles scheint klar und sauber. Ich muss keine Attribute oder Methoden definieren, bei denen ich sie nicht benötige. Die Basisknotenklasse kann schlank und gemein bleiben. Wo fange ich ohne RTTI an? Vielleicht kann ich der Basisklasse ein node_type-Attribut hinzufügen:
class node_base
{
public:
node_base();
virtual ~node_base();
std::vector< std::shared_ptr<node_base> > get_adjacent_nodes();
private:
std::string my_type;
};
Ist std :: string eine gute Idee für einen Typ? Vielleicht nicht, aber was kann ich noch verwenden? Erstelle eine Nummer und hoffe, dass sie noch niemand benutzt? Was ist bei meinem orange_node, wenn ich die Methoden von red_node und yellow_node verwenden möchte? Müsste ich mehrere Typen pro Knoten speichern? Das scheint kompliziert.
Fazit
Dieses Beispiel scheint nicht allzu komplex oder ungewöhnlich zu sein (ich arbeite an etwas Ähnlichem in meinem Tagesjob, bei dem die Knoten die tatsächliche Hardware darstellen, die über die Software gesteuert wird und die je nach dem, was sie sind, sehr unterschiedliche Aufgaben ausführen). Ich würde jedoch keinen sauberen Weg finden, dies mit Vorlagen oder anderen Methoden zu tun. Bitte beachten Sie, dass ich versuche, das Problem zu verstehen und mein Beispiel nicht zu verteidigen. Das Lesen von Seiten wie der SO-Antwort, die ich oben verlinkt habe, und dieser Seite in Wikibooks scheinen darauf hinzudeuten, dass ich RTTI missbrauche, aber ich würde gerne erfahren, warum.
Zurück zu meiner ursprünglichen Frage: Warum ist "reiner Polymorphismus" der Verwendung von RTTI vorzuziehen?
node_base
stelle fest, dass viele Antworten nicht die Idee berücksichtigen, dass Ihr Beispiel vorschlägt, Teil einer Bibliothek zu sein, und dass Benutzer ihre eigenen Knotentypen erstellen. Dann können sie keine Änderungen vornehmen node_base
, um eine andere Lösung zuzulassen. Vielleicht ist RTTI dann die beste Option. Auf der anderen Seite gibt es andere Möglichkeiten, eine solche Bibliothek so zu gestalten, dass neue Knotentypen viel eleganter passen, ohne dass RTTI verwendet werden muss (und auch andere Möglichkeiten, die neuen Knotentypen zu entwerfen).