Wie habe ich die Standard-C ++ 11-Bibliothek geschrieben oder warum ist Boost so beängstigend? Kapitel 2

Ja - ja, mit diesem Motto streiz ich mich in der Schlacht.

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ührung
Kapitel 1. Viam Supervadet Vadens
Kapitel 2. #ifndef __CPP11_SUPPORT__ #define __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endif
Kapitel 3. Finden der perfekten nullptr-Implementierung
Kapitel 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 // ??? C++11: #define __cplusplus 201103L C++14: #define __cplusplus 201402L C++17: #define __cplusplus 201703L 

Dementsprechend ist der Code zur Bestimmung der Verfügbarkeit von Support trivial:

 #if (__cplusplus >= 201103L) //  C++ 11   #define _STDEX_NATIVE_CPP11_SUPPORT //   11  (nullptr, static_assert) #define _STDEX_NATIVE_CPP11_TYPES_SUPPORT //    char16_t, char32_t #endif 

Bild 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 // C++98 standart C++98 + TR1: #define __cplusplus 200311L // C++03 nonstandart C++11: #define __cplusplus 200411L // C++03 + auto and dectype nonstandart C++11: #define __cplusplus 200511L // C++03 + auto, dectype and constexpr(partly) ... standart C++11: #define __cplusplus 201103L // C++11 

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 // Compatibility with non-clang compilers. #endif // Any compiler claiming C++11 supports, Visual C++ 2015 and Clang version supporting constexpr #if ((__cplusplus >= 201103L) || (_MSC_VER >= 1900) || (__has_feature(cxx_constexpr))) // C++ 11 implementation #define _STDEX_NATIVE_CPP11_SUPPORT #define _STDEX_NATIVE_CPP11_TYPES_SUPPORT #endif 

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 // Compatibility with non-clang compilers. #endif // Any compiler claiming C++11 supports, Visual C++ 2015 and Clang version supporting constexpr #if ((__cplusplus >= 201103L) || (_MSC_VER >= 1900) || (__has_feature(cxx_constexpr))) // C++ 11 implementation #define _STDEX_NATIVE_CPP11_SUPPORT #define _STDEX_NATIVE_CPP11_TYPES_SUPPORT #endif #if !defined(_STDEX_NATIVE_CPP11_TYPES_SUPPORT) #if ((__cplusplus > 199711L) || defined(__CODEGEARC__)) #define _STDEX_NATIVE_CPP11_TYPES_SUPPORT #endif #endif 

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 // Compatibility with non-clang compilers. #endif // Any compiler claiming C++11 supports, Visual C++ 2015 and Clang version supporting constexpr #if ((__cplusplus >= 201103L) || (_MSC_VER >= 1900) || (__has_feature(cxx_constexpr))) // C++ 11 implementation #define _STDEX_NATIVE_CPP11_SUPPORT #define _STDEX_NATIVE_CPP11_TYPES_SUPPORT #endif #if !defined(_STDEX_NATIVE_CPP11_TYPES_SUPPORT) #if ((__cplusplus > 199711L) || defined(__CODEGEARC__)) #define _STDEX_NATIVE_CPP11_TYPES_SUPPORT #endif #endif #if ((!defined(_MSC_VER) || _MSC_VER < 1600) && !defined(_STDEX_NATIVE_CPP11_SUPPORT)) #define _STDEX_IMPLEMENTS_NULLPTR_SUPPORT #else #define _STDEX_NATIVE_NULLPTR_SUPPORT #endif #if (_MSC_VER >= 1600) #ifndef _STDEX_NATIVE_CPP11_TYPES_SUPPORT #define _STDEX_NATIVE_CPP11_TYPES_SUPPORT #endif #endif 

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 // Compatibility with non-clang compilers. #endif // Any compiler claiming C++11 supports, Visual C++ 2015 and Clang version supporting constexpr #if ((__cplusplus >= 201103L) || (_MSC_VER >= 1900) || (__has_feature(cxx_constexpr))) // C++ 11 implementation #define _STDEX_NATIVE_CPP11_SUPPORT #define _STDEX_NATIVE_CPP11_TYPES_SUPPORT #endif #if !defined(_STDEX_NATIVE_CPP11_TYPES_SUPPORT) #if ((__cplusplus > 199711L) || defined(__CODEGEARC__)) #define _STDEX_NATIVE_CPP11_TYPES_SUPPORT #endif #endif #if ((!defined(_MSC_VER) || _MSC_VER < 1600) && !defined(_STDEX_NATIVE_CPP11_SUPPORT)) #define _STDEX_IMPLEMENTS_NULLPTR_SUPPORT #else #define _STDEX_NATIVE_NULLPTR_SUPPORT #endif #if (_MSC_VER >= 1600) #ifndef _STDEX_NATIVE_CPP11_TYPES_SUPPORT #define _STDEX_NATIVE_CPP11_TYPES_SUPPORT #endif #endif #if _MSC_VER // Visual C++ fallback #define _STDEX_NATIVE_MICROSOFT_COMPILER_EXTENSIONS_SUPPORT #define _STDEX_CDECL __cdecl #if (__cplusplus >= 199711L) #define _STDEX_NATIVE_CPP_98_SUPPORT #endif #endif // C++ 98 check: #if ((__cplusplus >= 199711L) && ((defined(__INTEL_COMPILER) || defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)))))) #ifndef _STDEX_NATIVE_CPP_98_SUPPORT #define _STDEX_NATIVE_CPP_98_SUPPORT #endif #endif 


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> { }; // StaticAssertion<true> template<int i> struct StaticAssertionTest { }; // StaticAssertionTest<int> } } 

und weitere Makromagie

 #ifdef _STDEX_NATIVE_CPP11_SUPPORT #define STATIC_ASSERT(expression, message) static_assert((expression), #message) #else // no C++11 support #define CONCATENATE(arg1, arg2) CONCATENATE1(arg1, arg2) #define CONCATENATE1(arg1, arg2) CONCATENATE2(arg1, arg2) #define CONCATENATE2(arg1, arg2) arg1##arg2 #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__) #ifndef _STDEX_NATIVE_NULLPTR_SUPPORT #define static_assert(expression, message) STATIC_ASSERT(expression, ERROR_MESSAGE_STRING) #endif #endif 

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 Ergebnisse
GCC:
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.

Source: https://habr.com/ru/post/de417099/


All Articles