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

Ja - ja, mit diesem Motto stürzte ich mich in die Schlacht.

Anstelle des Vorworts


Vielleicht sollte mit diesem Bild eine Geschichte über Boost , Loki , Independent und auch Implementierungen der mit Compilern gelieferten Standard-C ++ - Bibliothek beginnen.

Ja, ja, und wenn Sie dachten, dass die Entwickler der Standardbibliothek für dasselbe g ++, clang, Visual Studio oder, Gott sei Dank, C ++ Builder (früher Borland, aber der aktuelle Embarcadero) Gurus sind, die keine Krücken herstellen, brechen sie nicht den Standard für ihren Compiler Wenn Sie keine Fahrräder schreiben, verwenden Sie die Standard-C ++ - Bibliothek höchstwahrscheinlich nicht so aktiv, wie Sie gedacht haben.

Der Artikel ist als Geschichte geschrieben und enthält viel „Wasser“ und Abschweifungen, aber ich hoffe, dass meine Erfahrung und der daraus resultierende Code für diejenigen nützlich sind, die bei der Entwicklung in C ++ mit ähnlichen Problemen konfrontiert waren, insbesondere bei älteren Compilern. Link zu GitHub mit dem Ergebnis für heute für ungeduldige und Nichtleser:

https://github.com/oktonion/stdex (Commits und konstruktive Kritik sind willkommen)

Und jetzt das Wichtigste zuerst.


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
...

Eintrag


Es war 2017, C ++ 11 ist seit langem in einen neuen Strom neuer und relativ neuer Compiler eingebrochen, der standardisierte Arbeit mit Streams, Mutexen, erweiterte Vorlagenprogrammierung und Standardisierungsansätze bringt. Schließlich gibt es im Standard „große“ lange lange Typen Die weit verbreitete Notwendigkeit, Typen für den Compiler mithilfe von auto anzuzeigen (auf Wiedersehen std :: map <Typ, Typ> :: const_iterator it = ... - Sie verstehen mich), wurde beseitigt , und die Kombination dieser Funktion mit der neuen für jede ist zu einer der häufigsten geworden verwendete Loop-Iterator-Implementierungen. Schließlich konnten wir (Entwickler) dem Benutzer (Entwickler) menschlich mitteilen, warum der Code nicht mit static_assert sowie enable_if erfasst wird , das nun die erforderlichen Überladungen wie von Zauberhand auswählt.

Auf dem Hof ​​war 2017! Bereits C ++ 17 wurde aktiv in GCC, Clang, Visual Studio eingeführt, überall gab es Decltype (seit C ++ 11), Constexpr (seit C ++ 11, aber deutlich verbessert), die Module waren fast unterwegs, es gab eine gute Zeit. Ich war auf der Arbeit und habe mit einiger Missbilligung den nächsten internen Compiler-Fehler in meinem Borland C ++ Builder 6.0 sowie viele Build-Fehler mit der nächsten Version der Boost-Bibliothek untersucht. Ich denke, jetzt verstehst du, woher dieses Verlangen nach Fahrradbau kam. Wir haben Borland C ++ Builder 6.0 und Visual Studio 2010 für Windows, g ++ Version 4.4.2 oder niedriger für QNX und einige Unix-Systeme verwendet. Wir wurden von MacOS verschont, was zweifellos ein Plus war. Keine anderen Compiler (einschließlich C ++ 11) konnten aus Gründen, die wir außerhalb dieses Artikels belassen, überhaupt in Betracht gezogen werden.

"Und was könnte dort so kompliziert sein" - ein Gedanke schlich sich in meine erschöpften Versuche ein, unter dem guten alten Erbauerhirn Auftrieb zu geben. "Alles was ich brauche ist type_traits , thread , mutex , vielleicht chrono , nullptr wäre nett." Ich überlegte und machte mich an die Arbeit.

Kapitel 1. Viam Supervadet Vadens


Es war notwendig, von wo und von wo aus zu beginnen - natürlich hatte ich eine Reihe von Header-Dateien und Quellcodes über Projekte verteilt, mit Implementierungen ähnlicher oder identischer Funktionalität aus der Standard-C ++ 11-Standardbibliothek meiner Entwicklung sowie ehrlich ausgeliehen oder verarbeitet aus den Codes davon gleiche gcc und Boost. Durch die Kombination all dessen bekam ich ein Durcheinander von Funktionen, Klassen und Makros, die sich in eine elegante und schlanke Standardbibliothek verwandeln sollten. Nachdem ich den Arbeitsaufwand geschätzt hatte, entschied ich mich sofort, die Implementierung von allem und jedem aufzugeben und mich auf die Entwicklung eines „Add-Ons“ über die Standard-C ++ 98-Bibliothek zu beschränken, die mit dem Compiler geliefert wird.

In der ersten Version gab es keine besondere Einhaltung des Standards, hauptsächlich angewandte Probleme wurden gelöst. Zum Beispiel sah nullptr folgendermaßen aus:

#define nullptr 0 

static_assert wurde auch einfach gelöst:

  #define STATIC_ASSERT(expr) typedef int test##__LINE__##[expr ? 1 : -1]; 

std :: to_string wurde über std :: stringstream implementiert , das in Implementierungen ohne die sstream- Headerdatei durch std :: strstream ersetzt wurde, und all dies wurde sofort in den Namespace std verschoben :

  #ifndef NO_STD_SSTREAM_HEADER #include <sstream> #else #include <strstream> namespace std {typedef std::strstream stringstream;} #endif namespace std { template<class T> string to_string(const T &t) { stringstream ss; ss << t; return ss.str(); } } 

Es gab auch „Tricks“, die nicht im Standard enthalten waren, aber dennoch in der täglichen Arbeit nützlich waren, wie zum Beispiel die Ewigkeit oder die Anzahl der Makros :

  #define forever for(;;) //     #define countof(arr) sizeof(arr) / sizeof(arr[0]) //        C 

countof wurde dann in eine C ++ - Option umgewandelt:

  template <typename T, std::size_t N> char(&COUNTOF_REQUIRES_ARRAY_ARGUMENT(T(&)[N]))[N]; //        C++ (       ): #define countof(x) sizeof(COUNTOF_REQUIRES_ARRAY_ARGUMENT(x)) 

Die Arbeit mit Threads (der Header-Datei- Thread von std) wurde über einige der Tiny-Bibliotheken implementiert und unter Berücksichtigung der Funktionen des gesamten Compiler-Zoos und des Betriebssystems neu geschrieben. Und vielleicht ähnelte type_traits in gewissem Maße bereits dem, was der C ++ 11-Standard verlangte. Es gab std :: enable_if , std :: Integral_constant , std :: is_const und ähnliche Vorlagen, die bereits in der Entwicklung verwendet wurden.

  namespace std { template<bool Cond, class Iftrue, class Iffalse> struct conditional { typedef Iftrue type; }; // Partial specialization for false. template<class Iftrue, class Iffalse> struct conditional<false, Iftrue, Iffalse> { typedef Iffalse type; }; template <bool, class T = void> struct enable_if { }; template <class T> struct enable_if<true, T> { typedef T type; }; template<class Tp, Tp Val> struct integral_constant { // convenient template for integral constant types static const Tp value = Val; typedef const Tp value_type; typedef integral_constant<Tp, Val> type; }; typedef integral_constant<bool, true> true_type; typedef integral_constant<bool, false> false_type; template<bool Val> struct bool_constant : public integral_constant<bool, Val> { }; template<class, class> struct is_same : public false_type { }; template<class Tp> struct is_same<Tp, Tp> : public true_type // specialization { }; } // ...     

Es wurde beschlossen, alle nicht standardmäßigen und "Compiler" -Makros, Funktionen und Typen in eine separate Header-Datei core.h zu trennen. Und im Gegensatz zur Boost-Praxis, bei der das "Umschalten" von Implementierungen mithilfe von Makros weit verbreitet ist, werden Makros, die sich auf compilerabhängige Dinge beziehen, in allen Bibliotheksdateien außer core.h aufgegeben. Auch die Funktionalität, die nicht ohne die Verwendung von "Hacks" implementiert werden kann (Verstoß gegen den Standard, basierend auf undefiniertem Verhalten, um etwas definiert zu werden) oder für jeden Compiler einzeln implementiert wird (zum Beispiel durch seine eingebauten Makros), wurde beschlossen, nicht zur Bibliothek hinzuzufügen. um keinen weiteren monströsen (aber schönen) Schub zu erzeugen. Infolgedessen wird hauptsächlich und praktisch nur verwendet, ob core.h verwendet wird, um festzustellen, ob Unterstützung für integriertes nullptr vorhanden ist (da Compiler schwören, wenn reservierte Wörter überschrieben werden), Unterstützung für integriertes static_assert (erneut, um das Überschneiden eines reservierten Wortes zu vermeiden) und Unterstützung für integrierte C ++ - Typen 11 char16_t und char32_t .

Mit Blick auf die Zukunft kann ich sagen, dass die Idee fast ein Erfolg war, weil Das meiste, was in Boost durch harte Makros in Abhängigkeit von einem bestimmten Compiler definiert wird, wird in dieser Implementierung vom Compiler selbst in der Kompilierungsphase bestimmt.

Das Ende des ersten Kapitels. Im zweiten Kapitel werde ich die Erzählung über die Schwierigkeiten beim Umgang mit Compilern, über gefundene Krücken und elegante Lösungen im Darm von gcc, boost und Visual Studio sowie eine Beschreibung meiner Eindrücke von dem, was ich gesehen und Erfahrungen mit Codebeispielen gesammelt habe, fortsetzen.

Danke für die Aufmerksamkeit.

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


All Articles