
Zusammenfassung der vorherigen Teile
Aufgrund von Einschränkungen bei der Verwendung von C ++ 11-Compilern und mangelnder Alternativen wollte boost seine eigene Implementierung der Standard-C ++ 11-Bibliothek über die mit dem Compiler gelieferte C ++ 98 / C ++ 03-Bibliothek schreiben.
Zusätzlich zu den Standard-Header-Dateien
type_traits wurden
thread ,
mutex ,
chrono ,
nullptr.h hinzugefügt
, die
std :: nullptr_t und
core.h implementieren, wobei Makros für compilerabhängige
Funktionen hinzugefügt und die Standardbibliothek erweitert wurden.
Link zu GitHub mit dem Ergebnis für heute für ungeduldige und Nichtleser:
Engagements und konstruktive Kritik sind willkommen
Inhaltsverzeichnis
EinführungKapitel 1. Viam Supervadet VadensKapitel 2. #ifndef __CPP11_SUPPORT__ #define __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endifKapitel 3. Finden der perfekten nullptr-ImplementierungKapitel 4. C ++ Template Magic....
4.1 Wir fangen klein an....
4.2 Über wie viele wundersame Fehler das Protokoll für uns kompiliert....
4.3 Zeiger und alles in allem....
4.4 Was wird sonst noch für die Vorlagenbibliothek benötigt?Kapitel 5
...
Kapitel 2. #ifndef __CPP11_SUPPORT__ #define __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endif
Nachdem der gesamte Code ein wenig gekämmt und durch "Standard"
-Header in einen separaten
Namespace stdex unterteilt worden war, begann ich,
type_traits ,
nullptr.h und zusammen mit
core.h auszufüllen , das Makros enthielt, um die vom Compiler verwendete Version des Standards zu bestimmen und zu unterstützen
Native nullptr ,
char16_t ,
char32_t und
static_assert .
Theoretisch ist alles einfach - gemäß dem C ++ - Standard
( Abschnitt 14.8) muss das __cplusplus- Makro vom Compiler definiert werden und der Version des unterstützten Standards entsprechen:
C++ pre-C++98: #define __cplusplus 1 C++98: #define __cplusplus 199711L C++98 + TR1: #define __cplusplus 199711L
Dementsprechend ist der Code zur Bestimmung der Verfügbarkeit von Support trivial:
#if (__cplusplus >= 201103L)

Tatsächlich ist nicht alles so einfach und jetzt beginnen interessante Krücken mit einem Rechen.
Erstens implementieren nicht alle oder vielmehr keine der Compiler den nächsten Standard nicht vollständig und sofort. In Visual Studio 2013
fehlte constexpr beispielsweise sehr lange, während behauptet wurde, dass es C ++ 11 unterstützt - mit der Einschränkung, dass die Implementierung nicht abgeschlossen ist. Das heißt,
auto - bitte
static_assert - ist genauso einfach (auch von früheren MS VS),
constexpr jedoch
nicht . Zweitens stellen nicht alle Compiler (und das ist noch überraschender) diese Definition korrekt zur Verfügung und aktualisieren sie rechtzeitig. Unerwarteterweise hat Visual Studio im selben Compiler
die Version von __cplusplus define nicht von den ersten Versionen des Compilers an
geändert , obwohl die vollständige Unterstützung für C ++ 11 seit langem deklariert wurde (was auch nicht zutrifft, für die es separate Strahlen der Unzufriedenheit gibt - sobald die Konversation zu der spezifischen Funktionalität des "neuen" kommt "11 Standardentwickler sagen sofort, dass es keinen C99-Präprozessor gibt, es gibt keine anderen" Funktionen "). Und die Situation wird durch die Tatsache verschärft, dass Standard-Compiler diese Definition anders als die oben genannten Werte festlegen dürfen, wenn sie den deklarierten Standards nicht vollständig entsprechen. Es wäre logisch, beispielsweise eine solche Entwicklung von Definitionen für ein bestimmtes Makro anzunehmen (mit der Einführung neuer Funktionen erhöhen Sie die Zahl, die hinter dieser Definition verborgen ist):
standart C++98: #define __cplusplus 199711L
Gleichzeitig ist keiner der beliebtesten Compiler mit dieser Funktion "abgenutzt".
Aus diesem Grund (ich habe keine Angst vor diesem Wort) müssen Sie jetzt für jeden nicht standardmäßigen Compiler Ihre eigenen spezifischen Prüfungen schreiben, um herauszufinden, welcher C ++ - Standard und in welchem Umfang er unterstützt. Die gute Nachricht ist, dass wir nur einige Compilerfunktionen kennenlernen müssen, um richtig zu funktionieren. Zunächst fügen wir jetzt die Versionsprüfung für Visual Studio über das Makro
_MSC_VER hinzu , das für diesen Compiler eindeutig ist. Da es in meinem Arsenal an unterstützten Compilern auch C ++ Borland Builder 6.0 gibt, dessen Entwickler wiederum sehr darauf bedacht waren, die Kompatibilität mit Visual Studio (einschließlich seiner "Funktionen" und Fehler) aufrechtzuerhalten, gibt es plötzlich auch dieses Makro. Für Clang-kompatible Compiler gibt es ein nicht standardmäßiges Makro
__has_feature ( feature_name
) , mit dem Sie herausfinden können, ob der Compiler diese oder jene Funktionalität unterstützt. Infolgedessen wird der Code aufgeblasen auf:
#ifndef __has_feature #define __has_feature(x) 0
Möchten Sie mehr Compiler erreichen? Wir fügen Überprüfungen für Codegear C ++ Builder hinzu, der der Erbe von Borland ist (in seinen schlimmsten Erscheinungsformen, aber dazu später mehr):
#ifndef __has_feature #define __has_feature(x) 0
Es ist auch erwähnenswert, dass Visual Studio die
Nullptr- Unterstützung ab der Compiler-Version
_MSC_VER 1600 sowie die integrierten Typen
char16_t und
char32_t bereits implementiert hat und dies korrekt behandeln muss. Ein paar weitere Schecks hinzugefügt:
#ifndef __has_feature #define __has_feature(x) 0
Gleichzeitig werden wir prüfen, ob C ++ 98 unterstützt wird, da für Compiler ohne diese Unterstützung keine Header-Dateien der Standardbibliothek vorhanden sind und wir das Fehlen dieser Dateien nicht mithilfe des Compilers überprüfen können.
Volle Option #ifndef __has_feature #define __has_feature(x) 0
Und jetzt tauchen in meinem Gedächtnis umfangreiche Konfigurationen von Boost auf, in denen viele fleißige Entwickler all diese compilerabhängigen Makros geschrieben und eine Karte erstellt haben, was von einem bestimmten Compiler einer bestimmten Version unterstützt wird und was nicht, von der ich mich persönlich unwohl fühle. Ich möchte es nie mehr anschauen oder anfassen. Aber die gute Nachricht ist, dass Sie dort aufhören können. Zumindest reicht dies für mich aus, um die gängigsten Compiler zu unterstützen. Wenn Sie jedoch eine Ungenauigkeit feststellen oder einen weiteren Compiler hinzufügen möchten, nehme ich die Pull-Anfrage nur zu gerne an.
Eine großartige Leistung im Vergleich zu Boost. Ich glaube, dass es möglich war, die Verbreitung von compilerabhängigen Makros über den Code hinweg aufrechtzuerhalten, wodurch der Code sauberer und verständlicher wird und nicht Dutzende von Konfigurationsdateien für jedes Betriebssystem und für jeden Compiler gestapelt werden. Wir werden etwas später auf die Nachteile dieses Ansatzes eingehen.
Zu diesem Zeitpunkt können wir bereits beginnen, die fehlenden Funktionen aus den 11 Standards zu verbinden, und als erstes führen wir
static_assert ein .
static_assert
Wir definieren die
StaticAssertion- Struktur, die einen Booleschen Wert als Vorlagenparameter verwendet. Wenn diese Bedingung nicht erfüllt ist (der Ausdruck ist
falsch ), tritt beim Kompilieren einer nicht spezialisierten Vorlage ein Fehler auf. Und eine weitere Dummy-Struktur zum Empfangen von
sizeof ( StaticAssertion ) .
namespace stdex { namespace detail { template <bool> struct StaticAssertion; template <> struct StaticAssertion<true> { };
und weitere Makromagie
#ifdef _STDEX_NATIVE_CPP11_SUPPORT #define STATIC_ASSERT(expression, message) static_assert((expression), #message) #else
Verwendung:
STATIC_ASSERT(sizeof(void*) == 4, non_x32_platform_is_unsupported);
Ein wichtiger Unterschied zwischen meiner Implementierung und der Standardimplementierung besteht darin, dass dieses Schlüsselwort nicht überladen wird, ohne dies dem Benutzer mitzuteilen. Dies liegt an der Tatsache, dass es in C ++ unmöglich ist, mehrere Definitionen mit unterschiedlicher Anzahl von Argumenten als einem Namen zu definieren, und eine Implementierung ohne Nachricht ist viel weniger nützlich als die ausgewählte Option. Diese Funktion führt dazu, dass STATIC_ASSERT in meiner Implementierung im Wesentlichen die Version ist, die bereits in C ++ 11 hinzugefügt wurde.
Werfen wir einen Blick darauf, was passiert ist. Aufgrund der Überprüfung der Versionen von
__cplusplus- und nicht standardmäßigen Compilermakros verfügen wir über genügend Informationen zur C ++ 11-Unterstützung (und damit zum
static_assert ), ausgedrückt durch die
Definition _STDEX_NATIVE_CPP11_SUPPORT. Wenn dieses Makro definiert ist, können wir einfach den Standard
static_assert verwenden :
#ifdef _STDEX_NATIVE_CPP11_SUPPORT #define STATIC_ASSERT(expression, message) static_assert((expression), #message)
Beachten Sie, dass der zweite Parameter des STATIC_ASSERT- Makros überhaupt kein Zeichenfolgenliteral ist. Daher konvertieren wir den Nachrichtenparameter unter Verwendung des Präprozessoroperators # in eine Zeichenfolge zur Übertragung an den Standard static_assert .
Wenn wir keine Unterstützung vom Compiler haben, fahren wir mit unserer Implementierung fort. Zunächst deklarieren wir Hilfsmakros für das „Kleben“ von Strings (der Präprozessoroperator
## ist nur dafür verantwortlich).
#define CONCATENATE(arg1, arg2) CONCATENATE1(arg1, arg2) #define CONCATENATE1(arg1, arg2) CONCATENATE2(arg1, arg2) #define CONCATENATE2(arg1, arg2) arg1##arg2
Ich habe speziell nicht einfach #define CONCATENATE ( arg1 , arg2 ) arg1 ## arg2 verwendet, um das Ergebnis desselben CONCATENATE- Makros als Argument an arg1 und arg2 übergeben zu können .
Als nächstes deklarieren wir eine Struktur mit dem schönen Namen __static_assertion_at_line_ {Zeilennummer} (das Makro
__LINE__ wird ebenfalls durch den Standard definiert und muss auf die Zeilennummer erweitert werden, in der es aufgerufen wurde) und fügen innerhalb dieser Struktur ein Feld unseres Typs
StaticAssertion mit dem Namen STATIC_ASSERTION_FAILED_AT_LINE_ {Zeilennummer} _WITH hinzu Fehlermeldungen vom aufrufenden Makro}.
#define STATIC_ASSERT(expression, message)\ struct CONCATENATE(__static_assertion_at_line_, __LINE__)\ {\ stdex::detail::StaticAssertion<static_cast<bool>((expression))> CONCATENATE(CONCATENATE(CONCATENATE(STATIC_ASSERTION_FAILED_AT_LINE_, __LINE__), _WITH__), message);\ };\ typedef stdex::detail::StaticAssertionTest<sizeof(CONCATENATE(__static_assertion_at_line_, __LINE__))> CONCATENATE(__static_assertion_test_at_line_, __LINE__)
Mit dem Template-Parameter in
StaticAssertion übergeben wir einen Ausdruck, der in
STATIC_ASSERT überprüft wird und zu
bool führt . Um zu vermeiden, dass lokale Variablen erstellt werden und die Benutzerbedingung nicht mit Overhead überprüft wird, wird ein Alias für den Typ
StaticAssertionTest <sizeof ({Name der oben deklarierten Struktur}) mit dem Namen __static_assertion_test_at_line_ {Zeilennummer} deklariert.
Die ganze Schönheit der Benennung ist nur erforderlich, um aus einem Kompilierungsfehler klar zu machen, dass dies ein Assert-Ergebnis ist und nicht nur ein Fehler, sondern auch, um eine Fehlermeldung anzuzeigen, die für diesen Assert festgelegt wurde. Der
sizeof- Trick ist erforderlich, um den Compiler zu zwingen, die
StaticAssertion- Vorlagenklasse, die sich in der gerade deklarierten Struktur befindet, zu instanziieren und somit die zu bestätigende Bedingung zu überprüfen.
STATIC_ASSERT ErgebnisseGCC:
30: 103: Fehler: Feld 'STATIC_ASSERTION_FAILED_AT_LINE_36_WITH__non_x32_platform_is_unsupported' hat einen unvollständigen Typ 'stdex :: detail :: StaticAssertion <false>'
25:36: Anmerkung: In der Definition des Makros 'CONCATENATE2'
23.36 Uhr: Hinweis: In Erweiterung des Makros 'CONCATENATE1'
30:67: Hinweis: In Erweiterung des Makros 'CONCATENATE'
24.36 Uhr: Hinweis: In Erweiterung des Makros 'CONCATENATE2'
23.36 Uhr: Hinweis: In Erweiterung des Makros 'CONCATENATE1'
30:79: Hinweis: In Erweiterung des Makros 'CONCATENATE'
24.36 Uhr: Hinweis: In Erweiterung des Makros 'CONCATENATE2'
23.36 Uhr: Hinweis: In Erweiterung des Makros 'CONCATENATE1'
30:91: Hinweis: In Erweiterung des Makros 'CONCATENATE'
36: 3: Hinweis: In Erweiterung des Makros 'STATIC_ASSERT'
Borland C ++ Builder:
[C ++ - Fehler] stdex_test.cpp (36): E2450 Undefinierte Struktur 'stdex :: detail :: StaticAssertion <0>'
[C ++ - Fehler] stdex_test.cpp (36): E2449 Größe von 'STATIC_ASSERTION_FAILED_AT_LINE_36_WITH__non_x32_platform_is_unsupported' ist unbekannt oder Null
[C ++ - Fehler] stdex_test.cpp (36): E2450 Undefinierte Struktur 'stdex :: detail :: StaticAssertion <0>'
Visual Studio:
Fehler c2079
Der zweite "Trick", den ich haben wollte, obwohl
er im Standard
fehlt, ist
countof - das Zählen der Anzahl der Elemente im Array. Sisher deklarieren dieses Makro sehr gern durch sizeof (arr) / sizeof (arr [0]), aber wir werden noch weiter gehen.
countof
#ifdef _STDEX_NATIVE_CPP11_SUPPORT #include <cstddef> namespace stdex { namespace detail { template <class T, std::size_t N> constexpr std::size_t _my_countof(T const (&)[N]) noexcept { return N; } } // namespace detail } #define countof(arr) stdex::detail::_my_countof(arr) #else //no C++11 support #ifdef _STDEX_NATIVE_MICROSOFT_COMPILER_EXTENSIONS_SUPPORT // Visual C++ fallback #include <stdlib.h> #define countof(arr) _countof(arr) #elif defined(_STDEX_NATIVE_CPP_98_SUPPORT)// C++ 98 trick #include <cstddef> template <typename T, std::size_t N> char(&COUNTOF_REQUIRES_ARRAY_ARGUMENT(T(&)[N]))[N]; #define countof(x) sizeof(COUNTOF_REQUIRES_ARRAY_ARGUMENT(x)) #else #define countof(arr) sizeof(arr) / sizeof(arr[0]) #endif
Für Compiler mit
constexpr- Unterstützung deklarieren
wir eine constexpr-Version dieser Vorlage (was für alle Standards absolut nicht erforderlich ist, die Implementierung über die
COUNTOF_REQUIRES_ARRAY_ARGUMENT- Vorlage ist
ausreichend ), für den Rest führen wir die Version über die Vorlagenfunktion
COUNTOF_REQUIRES_ARRAY_ARGUMENT ein . Visual Studio zeichnet sich hier wiederum durch das Vorhandensein einer eigenen Implementierung von
_countof in der Header-Datei
stdlib.h aus .
Die Funktion
COUNTOF_REQUIRES_ARRAY_ARGUMENT sieht einschüchternd aus und es ist ziemlich schwierig
herauszufinden , was sie tut. Wenn Sie genau hinschauen, können Sie verstehen, dass ein Array von Elementen des Vorlagentyps
T und der Größe
N das einzige Argument ist. Wenn Sie also andere Elementtypen (keine Arrays) übertragen, erhalten Sie einen Kompilierungsfehler, der zweifellos gefällt. Bei näherer Betrachtung können Sie (mit Schwierigkeiten) feststellen, dass ein Array von
Zeichenelementen der Größe
N zurückgegeben wird. Die Frage ist, warum brauchen wir das alles? Hier kommt die
Größe des Operators ins
Spiel und seine einzigartige Fähigkeit, zur Kompilierungszeit zu arbeiten. Die
Größe des Aufrufs
( COUNTOF_REQUIRES_ARRAY_ARGUMENT ) bestimmt die Größe des von der Funktion zurückgegebenen Arrays von
Zeichenelementen. Da die Standardgröße von
(char) == 1 ist, ist dies die Anzahl von
N Elementen im ursprünglichen Array. Elegant, schön und völlig kostenlos.
für immer
Ein weiteres kleines Hilfsmakro, das ich überall dort verwende, wo eine Endlosschleife benötigt wird, ist
für immer . Es ist wie folgt definiert:
#if !defined(forever) #define forever for(;;) #else #define STRINGIZE_HELPER(x) #x #define STRINGIZE(x) STRINGIZE_HELPER(x) #define WARNING(desc) message(__FILE__ "(" STRINGIZE(__LINE__) ") : warning: " desc) #pragma WARNING("stdex library - macro 'forever' was previously defined by user; ignoring stdex macro definition") #undef STRINGIZE_HELPER #undef STRINGIZE #undef WARNING #endif
Beispielsyntax zum Definieren einer expliziten Endlosschleife:
unsigned int i = 0; forever { ++i; }
Dieses Makro wird ausschließlich zum expliziten Definieren einer Endlosschleife verwendet und ist nur aus Gründen des „Hinzufügens von syntaktischem Zucker“ in der Bibliothek enthalten. In Zukunft schlage ich vor, es durch optionales Definieren des
FOREVER- Plug-In-Makros zu ersetzen. Was im obigen Code-Snippet aus der Bibliothek bemerkenswert ist, ist dasselbe
WARNING- Makro, das in allen Compilern eine Warnmeldung generiert, wenn das Makro für immer bereits vom Benutzer definiert wurde. Es verwendet das bekannte Standardmakro
__LINE__ und das Standardmakro
__FILE__ , die in eine Zeichenfolge mit dem Namen der aktuellen Quelldatei konvertiert werden.
stdex_assert
Um
assert in Runtime zu implementieren, wird das Makro
stdex_assert wie folgt eingeführt:
#if defined(assert) #ifndef NDEBUG #include <iostream> #define stdex_assert(condition, message) \ do { \ if (! (condition)) { \ std::cerr << "Assertion `" #condition "` failed in " << __FILE__ \ << " line " << __LINE__ << ": " << message << std::endl; \ std::terminate(); \ } \ } while (false) #else #define stdex_assert(condition, message) ((void)0) #endif #endif
Ich werde nicht sagen, dass ich sehr stolz auf diese Implementierung bin (sie wird in Zukunft geändert werden), aber hier wurde eine interessante Technik verwendet, auf die ich aufmerksam machen möchte. Um die Prüfungen vor dem Geltungsbereich des Anwendungscodes zu verbergen, wird das Konstrukt
do {} while (false) verwendet, das ausgeführt wird. Dies ist einmal offensichtlich und führt gleichzeitig keinen "Service" -Code in den allgemeinen Anwendungscode ein. Diese Technik ist sehr nützlich und wird an mehreren anderen Stellen in der Bibliothek verwendet.
Andernfalls ist die Implementierung dem Standard-
Assert sehr ähnlich. Mit einem bestimmten
NDEBUG- Makro, das Compiler normalerweise in Release-Builds festlegen, führt assert nichts aus, andernfalls unterbricht es die Ausführung des Programms mit der Nachrichtenausgabe an den Standardfehlerstrom, wenn die Assert-Bedingung nicht erfüllt ist.
keine Ausnahme
Für Funktionen, die keine Ausnahmen
auslösen , wurde das Schlüsselwort
noexcept in den neuen Standard aufgenommen. Es ist auch ganz einfach und schmerzlos möglich, über das Makro zu implementieren:
#ifdef _STDEX_NATIVE_CPP11_SUPPORT #define stdex_noexcept noexcept #else #define stdex_noexcept throw() #endif
Es ist jedoch zu verstehen, dass
noexcept standardmäßig den Wert
bool annehmen kann und auch verwendet
werden kann , um zur Kompilierungszeit zu bestimmen, dass der an ihn übergebene Ausdruck keine Ausnahme
auslöst . Diese Funktionalität kann ohne Compiler-Unterstützung nicht implementiert werden. Daher gibt es in der Bibliothek nur ein "abgespecktes"
stdex_noexcept .
Das Ende des zweiten Kapitels. Das
dritte Kapitel befasst sich mit den Feinheiten der nullptr-Implementierung, warum sie für verschiedene Compiler unterschiedlich ist, sowie mit der Entwicklung von type_traits und welchen anderen Fehlern in den Compilern, auf die ich während ihrer Entwicklung gestoßen bin.
Danke für die Aufmerksamkeit.