Strikter C++-Compiler (Clang-basierte C++-Compiler)

Aus Appmethod Topics
Wechseln zu: Navigation, Suche

Nach oben zu Unterschiede zwischen Clang-basierten C++-Compilern und C++-Compilern der vorherigen Generation


Clang-basierte C++-Compiler basieren auf Clang. Clang-basierte C++-Compiler unterstützen C++11 und sind zudem striktere Compiler als BCC32.

Strukturelle Syntax

In BCC32 können Sie __try und catch miteinander verwenden.

In Clang-basierten C++-Compilern müssen Sie try/catch (Standard-C++) oder __try/__except/__finally (Microsoft-Erweiterungen) verwenden.

Typumwandlungen

Beim Zuweisen eines literalen C-Strings zu char * wird eine Warnung erzeugt, wenn dieser Zeiger nicht als const deklariert ist.

BCC32 lässt die Zuweisung zwischen char * und unsigned char* ohne Warnung zu; aber in Clang-basierten C++-Compilern ist dies ein einfacher Typübereinstimmungsfehler.

Die implizite Umwandlung von int in (klassisch, nicht typsicher) enum ist in BCC32 mit einer Warnung zulässig. In Clang-basierten C++-Compilern ist die implizite Umwandlung nicht zulässig und erfordert eine Typumwandlung. Häufig ist dies bei Enums wie TColor und TCursor der Fall:

TColor stones = (TColor) 0;  // instead of defaulting to random color, paint it black
                              // Note: Vcl.Graphics.hpp does define the TColor clBlack

Definieren von statischen Membern

Statische C++-Daten-Member dürfen nur an einer Stelle in einer Anwendung definiert werden. Sie sollten sie in der Klassendeklaration in einem Header deklarieren und dann den Daten-Member in einer Quelldatei (nicht in einer Header-Datei) definieren.

Zum Beispiel:

foo.h:
 struct X { static int static_member; };
 
 foo.cpp:
 #include "foo.h"
 int X::me;
 
 bar.cpp:
 #include "foo.h"
 int some_function() { return X::me; }

In diesem Beispiel hat foo.cpp die Definition X::me, und bar.cpp enthält eine Verwendung. Der statische Member ist in foo.h deklariert, aber nur in foo.cpp definiert.

Bei den 32-Bit-Tools sind mehrere Definitionen von X::me zulässig, was eine Verletzung des C++-Standards darstellt. Sie könnten also Folgendes angeben:

foo.h:
 struct X { static int static_member; };
 int X::me;

Im obigen Beispiel (fehlerhaft für Clang-basierte C++-Compiler, aber in BCC32 zulässig) wird der statische Daten-Member im Header definiert, was zu mehreren Definitionen von X::me führt, eine Definition an jeder Stelle, an der der Header einbezogen wird. Bei den Clang-basierten Tools ist dies nicht zulässig, und Sie könnten Linker-Fehler wie diesen erhalten: [ilink64 Error] Error: Public symbol 'triplet::sum' defined in both module C:\USERS\WIN764\NODES.O and C:\USERS\WIN764\UNIT1.O

Wenn Sie diese Fehler hinsichtlich der doppelten Symboldefinition vom Linker für statische Daten-Member erhalten, sollten Sie nach dem Fehler entsprechend dem obigen Beispiel suchen und den Code so korrigieren, dass nur eine Definition für statische Daten-Member vorhanden ist.

Zweiphasige Namenssuche in Templates

Die Namensauflösung wird in Templates zu zwei unterschiedlichen Zeitpunkten versucht: bei der Definition und der Instantiierung. Weniger konforme Compiler verschieben die Auflösung eher bis zur Instantiierung und ermöglichen so manchmal, dass nicht strikt dem Standard entsprechender Code erfolgreich compiliert wird. BCC32 compiliert eine Template erst nach deren Instantiierung, daher können Template-Definitionen, die sogar offensichtliche Fehler enthalten, compiliert werden, wenn sie nie verwendet werden. BCC64 meldet diese Fehler.

Die zweiphasige Namenssuche hat einige subtile Effekte, wie z.B. dass Basisklassen nicht überprüft werden (siehe this LLVM blog entry). Die Lösung für dieses spezielle Problem liegt in der Qualifizierung von in Basisklassen vorhandenen Bezeichnern mit this->.

Qualifizierte abhängige Typen erfordern das Schlüsselwort typename

Was diese Template ausführt:

template <class T>
 void pointerOrMultiply() {
   T::member *var;
 }

Vermutlich war die Erstellung eines Zeigers auf ein Member des Template-Typs beabsichtigt, aber mit einigen template-Parametern könnte dies auch eine Multiplikation sein, deren Ergebnis verworfen wird. Um die Zweideutigkeit, die erst nach der Instantiierung der Template aufgelöst werden kann, zu vermeiden, verlangt der Standard, dass qualifizierten vom template-Parameter abhängigen Typen das Schlüsselwort typename vorangestellt werden muss, auch wenn diese Interpretation die einzig sinnvolle während der Instantiierung ist:

template <class T>
 void definitelyAPointer() {
   typename T::member *var;
 }

Einfügen von Token

Für Clang-basierte C++-Compiler muss die Regel eingehalten werden, dass das Einfügen eines Token durch den Präprozessor (mit dem Operator ##) ein einzelnes gültiges Token ergeben muss.

Hier ist ein konstruiertes Beispiel dafür: Angenommen, die Codebasis definiert ein funktionsähnliches Makro, das konditional einen Namen wie angegeben verwendet oder ihn mit einem Namespace qualifiziert:

#ifdef KEEP_IT_SIMPLE
   #define qual(C,M) M
 #else
   #define qual(C,M) C##::##M
 #endif
 
 // ...
 
 void notRecommendedJustAnExample(int counter) {
   qual(std, cout) << counter;
 }

BCC32 compiliert den letzteren Fall, und das Beispiel "arbeitet wie erwartet". Aber eigentlich wird dies nicht offiziell unterstützt; Clang-basierte C++-Compiler geben die folgenden Fehlermeldungen aus:

pasting formed 'std::', an invalid preprocessing token
pasting formed '::cout', an invalid preprocessing token

Zwei Instanzen von ## haben zwei Fehler hervorgerufen. (Hinweis: Die Reihenfolge von mehreren Token-Einfügeoperationen in einen einzelnen Makroaufruf ist nicht definiert.) Keine der beiden ergibt ein einzelnes Token; beide versuchen, einen Bezeichner und ein Interpunktionszeichen (den Zugriffsbereichsoperator) zu kombinieren.

Fälle wie diese können schnell korrigiert werden: Das Einfügen von Token ist eigentlich nicht notwendig. Der Bezeichner und der Operator liegen am Ende sowieso nebeneiander. Das Einfügen ist nur erforderlich, um ein neues Token zu erstellen (mit dem dann weitere Makroerweiterungen vorgenommen werden). Daher könnte das Makro folgendermaßen definiert werden:

#define qual(C,M) C::M

Siehe auch