Ich habe kürzlich an einem Stück C ++ - Code für ein Nebenprojekt gearbeitet (die cpp-markdown
-Bibliothek , z der Neugierige) und stieß auf eine Coding-Frage, über die ich gerne ein paar Meinungen hätte.
cpp-markdown
hat eine Basisklasse namens Token
, die eine Reihe von Unterklassen hat. Zwei der Hauptunterklassen sind Container
(enthält Sammlungen anderer Token
s) und TextHolder
(wird als Basisklasse für Token
s verwendet, die natürlich Text enthalten).
Der Großteil der Verarbeitung wird über virtuelle Funktionen abgewickelt, aber einige davon wurden besser in einer einzigen Funktion behandelt. Dafür habe ich dynamic_cast
benutzt, um den Zeiger von Token*
auf eine seiner Unterklassen down-casten zu lassen, damit ich Funktionen aufrufen konnte, die spezifisch für die Unterklasse und ihre Kindklassen sind. Es besteht keine Chance, dass der Casting fehlschlägt, weil der Code über virtuelle Funktionen (wie isUnmatchedOpenMarker
) erkennen kann, wann so etwas benötigt wird.
Es gibt zwei andere Möglichkeiten, wie ich das handhaben könnte:
Erstellen Sie alle der Funktionen, die ich als virtuelle Funktionen von Token
bezeichnen möchte, und lassen Sie sie nur für jede Unterklasse mit Ausnahme des oder der benötigten Elemente leer um sie zu behandeln, oder ...
Erstellen Sie eine virtuelle Funktion in Token
, die den korrekt typisierten Zeiger auf this
zurückgibt, wenn er in bestimmten Subtypen aufgerufen wird, und einen Nullzeiger, wenn er für etwas anderes aufgerufen wird. Grundsätzlich eine Erweiterung des virtuellen Funktionssystems, das ich dort bereits benutze.
Die zweite Methode scheint mir besser zu sein als die bestehende und die erste. Aber ich würde gerne die Ansichten anderer erfahrener C ++ - Entwickler darüber erfahren. Oder ob ich mir zuviel Sorgen wegen Nebensächlichkeiten mache. : -)
# 1 verschmutzt den Klassennamespace und die vtable für Objekte, die sie nicht benötigen. Ok, wenn Sie eine Handvoll Methoden haben, die im Allgemeinen implementiert werden, aber einfach hässlich, wenn sie nur für eine einzelne abgeleitete Klasse benötigt werden.
# 2 ist nur dynamic_cast<>
in einem gepunkteten Kleid und Lippenstift. Client-Code wird nicht einfacher und verwickelt die gesamte Hierarchie, sodass die Basis und jede abgeleitete Klasse halb jeder anderen abgeleiteten Klasse sein müssen.
Benutze einfach dynamic_cast<>
. Dafür ist es da.
Wenn Sie wissen, dass die Konvertierung nicht ungültig sein kann, verwenden Sie einfach static_cast.
Wenn Sie clever werden möchten, können Sie auch ein Double-Dispatch -Muster erstellen, das zwei Drittel des Besuchermuster .
TokenVisitor
, die leere virtuelle visit(SpecificToken*)
-Methoden enthält. accept(TokenVisitor*)
-Methode hinzu, die die korrekt typisierte Methode für den übergebenen TokenVisitor aufruft. Für das vollständige Besuchermuster, das für Baumstrukturen nützlich ist, durchlaufen die standardmäßigen accept
-Methoden die untergeordneten Elemente, die token->accept(this);
für jedes aufrufen.
Warum möchten Sie dynamic_cast
vermeiden? Verursacht dies einen inakzeptablen Flaschenhals bei Ihrer Anwendung? Wenn nicht, ist es vielleicht nicht wert, etwas mit dem Code zu tun.
Wenn es Ihnen recht ist, ein wenig Sicherheit für ein bisschen Geschwindigkeit in Ihrer speziellen Situation einzutauschen, sollten Sie in der Lage sein, eine static_cast
; Das zementiert jedoch deine Annahme, dass du den Typ des Objekts kennst und es keine Chance gibt, dass die Besetzung schlecht ist. Wenn Ihre Annahme später falsch wird, könnten Sie in Ihrem Code einige mysteriöse Absturzfehler haben. Zurück zu meiner ursprünglichen Frage, bist du dir wirklich sicher, dass sich der Trade hier lohnt?
Wie für die Optionen, die Sie aufgelistet haben: Das erste klingt nicht wirklich nach einer Lösung, sondern nach einem Last-Minute-Hack, den ich erwarten würde, wenn jemand um 3 Uhr morgens Code schreibt. Die Funktionalität, die sich in Richtung der Basis der Klassenhierarchie schlängelt, ist einer der häufigsten Anti-Pattern-Treffer von Leuten, die neu in OOP sind. Tu es nicht.
Bei der zweiten Option, die Sie aufgelistet haben, wird dynamic_cast
wirklich neu implementiert - wenn Sie auf einer Plattform mit nur Mistkompilern sind (ich habe gehört, dass der Compiler des Gamecube ein Viertel des verfügbaren Systems belegt) RAM mit RTTI info) das könnte sich lohnen, aber mehr als wahrscheinlich verschwenden Sie nur Ihre Zeit. Bist du dir wirklich sicher, dass es sich hier um etwas handelt, über das du dich besinnen solltest?
Die wirklich saubere Möglichkeit, dynamic_cast zu verhindern, besteht darin, Zeiger des richtigen Typs an der richtigen Stelle zu haben. Abstraktion sollte sich um den Rest kümmern.
IMHO, der Grund dafür, dass dynamic_cast diese Reputation hat, ist, dass seine Leistung jedes Mal ein wenig abnimmt, wenn Sie einen weiteren Untertyp in Ihrer Klassenhierarchie hinzufügen. Wenn Sie 4-5 Klassen in der Hierarchie haben, brauchen Sie sich keine Sorgen zu machen.
Lustige Sachen. Die dynamic_cast
im Tokenizer impliziert, dass Sie Token auf der Grundlage der aktuellen Position im Textstream und der Logik in Token in Token teilen möchten. Jeder Token ist entweder eigenständig oder muss wie oben beschrieben einen Token erstellen, um Text korrekt zu verarbeiten.
Auf diese Weise ziehen Sie das generische Zeug heraus und können immer noch basierend auf der Hierarchie der Token analysieren. Sie können diesen ganzen Parser sogar datengesteuert machen, ohne dynamic_cast
zu benutzen.