C++ Erbschafts-Steuer
« | 25 Jun 2023 | »Politisch kann ich mich mit der Idee der Erbschaftssteuer gut identifizieren, vermutlich deshalb weil mich kein Millionenerbe erwartet.
In Sachen C++ sollte jedenfalls jede unnötige Form von Erbschaft rigoros weggesteuert werden. Und im Gegensatz zur Politik mache ich gerne Nägel mit Köpfen und setze diese Ziel einfach um.
Und wieder einmal hat mich der ehrwürdige alte Zen-Meister OpenWatcom auf eine Lebensweisheit aufmerksam gemacht:
code_bloat = exceptions * inheritance
Vorgeschichte: Welche Ausnahmen gibt es überhaupt?
Anfangs dachte ich, dass Exceptions im Java-Style “übersichtlich” wären.
Es gibt im GATE Framework daher eine abstrakte Throwable
Klasse, von der
eine Exception
Basisklasse erbt, und dann gibt es im Namensraum exceptions
noch für jeden Result-Code eine Template-Variante, wie z.B.:
exceptions::NotImplemented
exceptions::NullPointer
exceptions::Timeout
exceptions::OutOfBounds
- usw.
In Wahrheit sind die exceptions::
Varianten nur mit einem Result-Code
konstruierte Exception(results::ErrorCode)
Instanzen.
Zwar habe ich schon häufiger die “Endknoten” per throw
geworfen, doch in
den catch
Blöcken reagiere ich in erste Linie auf Instanzen der
Exception
Basisklasse.
Der Grund ist einfach: In fast allen Fällen ist es egal, ob z.B.:
ein Dateizugriff, wegen eines AccessDenied
oder NotAvailable
Fehlers
abbricht. Man loggt den Fehler und beendet die Operation. Es bringt mir
also im Normalfall nichts zwischen den Fehlern zu unterscheiden und falls
doch, tut das ein einfaches if
ebenso.
Die eine Exception, die viele ist…
Ich könnte also gleich die Basisklasse mit dem entsprechenden Fehlercode werfen. Und um das umzusetzen, nutze ich eine Funktion namens
1void raiseException(result_t resultCode, char const* msg = 0, char const* src = 0, int32_t errCode = 0);
Die machte bisher nichts anderes als den resultCode
Parameter auszuwerten und daraus ein
throw exceptions::Something(msg, src, errCode);
auszulösen.
Und seit neuem macht sie nur noch:
throw Exception(resultCode, msg, src, errCode);
Und das alleine reichte, um die DOS Varianten meiner GATE Tools um 70 bis 80 KByte zu verkleinern. So wurden aus 290 KB wieder 215 KB, ohne dass sich funktional was änderte.
RTTI und Catch-Blöcke
Mit jeder neuen abgeleiteten Klasse einer abstrakten Basis, wird ein unsichtbares Netz von Typenverknüpfungen um Hintergrund gesponnen, das dann zum Einsatz kommt, wenn zur Laufzeit eine Exception geworfen und schrittweise als ihr Basistyp zum Catch-Block überführt werden muss.
Je mehr Klassen es gibt um so größer wird der generierte Hintergrundcode, der dann am Ende vielleicht nie benutzt wird, aber trotzdem vorhanden sein muss.
Jetzt könnte man natürlich hoffen, dass ein guter Optimizer den Großteil davon wegbekommt, doch wie sich zeigt, passiert das nicht (bzw. nicht immer).
Am einfachsten kann man den Müllberg also dadurch klein halten, dass man im Vorfeld gar keinen Müll mehr produziert. Und dieser Ansatz funktioniert offenbar zu meiner großen Freude.
Fazit
Ich habe die früheren exceptions::Something
Klassen per #ifdef
deaktiviert,
denn vielleicht finde ich in Zukunft doch noch Verwendung für sie.
Aber das DOS Beispiel hat mich bereits davon überzeugt, dass
“eine” gute Exceptionklasse viel Effizienter ist, als 20 individuelle.
Ich belasse daher die vereinfache Variante und freue mich, dass der ohnehin große Exceptionoverhead wieder ein Stück erträglicher geworden ist.