 
  
  noexcept und constexpr
«« | 22 Aug 2021 | »»noexcept und constexpr kamen mit C++11
in den Standard und sind zwei ganz tolle Features. Doch wegen der 
Abwärtskompatibilität bleibt mir deren Nutzung im GATE Projekt eigentlich 
verwehrt.
… oder eben nicht, wenn man ein bisschen trickst.
throw() != noexcept
Im MSVC könnte man eigentlich
fragen: Wozu braucht man noexcept, 
wenn die throw() Exception-Specification 
ohnehin schon Exception deaktiviert?
Es war tatsächlich eine Microsoft-spezifische Erweiterung (oder genau genommen 
eine Reduktion), die bei einer Funktion mit throw() Angabe das Generieren 
von Exception-spezifischem-Binärcode einfach weggelassen hat.
In MSVC war also das Werfen einer Exception innerhalb einer noexcept
Funktion “undefiniert” bzw. crash-reif, hat dafür aber schnell Code erzeugt,
weil Exceptions nicht behandelt wurden.
Doch gemäß Standard muss auf Exception zur Laufzeit immer geachtet werden: Doch gemäß Standard muss auf Exception zur Laufzeit immer geachtet werden:
- throw()ist eine Laufzeit Abweisung (Runtime) und sollte eigentlich immer eine Exception-Behandlung im Binärcode generieren und wenn in der Funktion eine nicht-erwartete Exception auftritt, muss- std::unexpected()aufgerufen werden, wo man seinen eigenen Handler einsetzen könnte oder im Standardfall- std::terminate()am Ende herauskommt.
- noexceptbewirkt während der Übersetzung (Compiling) eine Prüfung, ob interner Code Exceptions wirft und gibt Warnungen aus, wenn eine- noexceptFunktion einen nicht-- noexceptCode nutzt. Ignoriert man die Warnung landet man auch bei- std::terminate()bei einer Exception aber zumindest ohne weitere Umwege und ohne viel Hintergrundcode.
const <= constexpr
constexpr legt fest, dass der Wert eines Types schon beim Kompilieren
fix feststeht und das Ergebnis konstant bleibt und damit auch z.B. in 
Templates als Parameter genutzt werden kann.
(Typische Beispiele mit Compile-Time-Faktoriellen-Berechnungen mit constexpr 
sind offen gesagt ein Hohn und deshalb lasse ich sie hier weg. Denn seit dem 
Jahr 2000 schaffen es fast alle Compiler solche Funktionen auch mit einem
normalen const jeglichen Laufzeitcode wegzuoptimieren.)
constexpr garantiert, dass das Ergebnis einer Operation konstant ist 
und bleibt und im Idealfall finden wir bei dessen Nutzung im Binärcode dann 
ebenfalls nur mehr eine CPU Operation, die einen statischen Wert nutzt.
Der Compiler kann also den Aufruf einer Funktion vollständig wegoptimieren
und während das bei const alleine ein Glücksspiel ist, haben wir bei 
constexpr de facto eine Garantie für dieses Optimum.
Makros für Gestern
Tja, und wenn man jetzt mit MSVC 2005 kompiliert, führen noexcept und 
constexpr zu Fehlern, da er sie einfach nicht kennt.
Doch wenn man auf alten Compilern ein einfaches
nutzt, werden zumindest ein paar modernere C++ Zeilen auch in antiken Varianten nutzbar. Sie generieren dort zwar nicht die gleichen guten Ergebnisse, gestatten aber zumindest eine formal korrekte Übersetzung.
So kann also problemlos noexcept an Funktionen angefügt werden und andere
Funktionen dürfen constexpr beim Rückgabetyp angeben.
Somit kann ich zumindest ein paar neue Ausdrücke im GATE Projekt unterbringen.
Die Nutzung von constexpr Werten als Template-Parameter ist und bleibt 
aber leider weiter Tabu.
Spezialfall MSVC 2013, Normalfall ab MSVC 2015
Und hier lernen wir wieder mal über Code-Archäologie, wie Microsoft nur 
schleppend die neuen Standards umsetzte.
noexcept wurde erst mit MSVC 2015 voll unterstützt, also brauchen ältere
MSVC Compiler also das obige Makro.
Doch leider galt noexcept als reserviertes Schlüsselwort und dessen Nutzung
war in MSVC 2013 per #define deshalb nicht erlaubt.
Dafür musste man das Makro _ALLOW_KEYWORD_MACROS auf 1 definieren um 
künstlich generierte Compiler-Fehler abzuwehren.
Fazit
constexpr haben in GATE aktuell keine große Bedeutung, da viele relevante 
Konstanten im C-Teil definiert sind und eigentlich nie über Funktionen
generiert werden.
Und für Integer-Konstanten in Templates gibt es ein besseres kompatibleres
Feature: nämlich enum. Enum Werte sind Konstanten, Header-only-tauglich
und von C89 bis C++20 perfekt unterstützt. Ich ziehe daher ein 
enum { abc = 42 }; einem constexpr int abc = 42 vor.
noexcept hingegen ist sehr nützlich um besonders auf älteren Compilern 
unnötigen Exception-Overhead zu reduzieren. Vor allem die Warnung bei 
Nichteinhaltung einer noexcept-Aufrufkette hilft enorm um Code sauber 
zu halten.
Zwar sollten klassische extern C Funktionen generell als noexcept 
bzw. throw() angenommen werden, doch gelegentlich wird dennoch im C++
Teil Exceptioncode generiert, der nie ausgeführt werden wird. Daher sollte
der Zusatz nie fehlen, wenn man keine Exceptions einsetzt.
Gerade auf 32-bit Systemen, wo Exceptionhandler über Zugriffe auf das F
Segment registriert werden, reduziert man so unnötig viel sinnlosen Code.
Tja … was man nicht alles aus Assembler-Dumps lernen kann …
