Comment ai-je écrit la bibliothèque C ++ 11 standard ou pourquoi boost est si effrayant. Présentation

Oui - oui, avec cette devise, je me suis précipité dans la bataille.

Au lieu de l'avant-propos


Peut-être qu'avec cette image, toute histoire de boost , Loki , indépendant, ainsi que les implémentations de la bibliothèque C ++ standard fournie avec les compilateurs devraient commencer.

Oui, oui, et si vous pensiez que les développeurs de la bibliothèque standard pour le même g ++, clang, Visual Studio ou, Dieu me pardonne, C ++ Builder (anciennement Borland, mais l'actuel Embarcadero) sont des gourous qui ne font pas de béquilles, ils ne cassent pas la norme pour leur compilateur et n'écrivez pas de vélos, alors vous n'utilisez probablement pas aussi activement la bibliothèque C ++ standard que vous le pensiez.

L'article est écrit comme une histoire et contient beaucoup «d'eau» et de digressions, mais j'espère que mon expérience et le code résultant seront utiles à ceux qui ont rencontré des problèmes similaires lors du développement en C ++, en particulier sur les anciens compilateurs. Lien vers GitHub avec le résultat d'aujourd'hui pour les impatients et les non-lecteurs:

https://github.com/oktonion/stdex (les commits et les critiques constructives sont les bienvenus)

Et maintenant, tout d'abord.


Table des matières


Présentation
Chapitre 1. Viam supervadet vadens
Chapitre 2. #ifndef __CPP11_SUPPORT__ #define __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endif
Chapitre 3. Recherche de l'implémentation nullptr parfaite
Chapitre 4. Magie des modèles C ++
.... 4.1 On commence petit
.... 4.2 À propos du nombre d'erreurs miraculeuses que le journal compile pour nous
.... 4.3 Pointeurs et tout-tout
.... 4.4 Quoi d'autre est nécessaire pour la bibliothèque de modèles
Chapitre 5
...

Entrée


C'était en 2017, C ++ 11 a depuis longtemps fait irruption dans un nouveau flux de compilateurs nouveaux et relativement nouveaux, apportant un travail standardisé avec des flux, des mutex, en élargissant la programmation de modèles et en standardisant les approches, il existe enfin de «grands» types longs et longs dans la norme , enfin s'est débarrassé du besoin généralisé d'afficher les types du compilateur en utilisant auto (au revoir std :: map <type, type> :: const_iterator it = ... - eh bien, vous me comprenez), et la combinaison de cette fonctionnalité avec la nouvelle pour chacun est devenue l'une des plus courantes implémentations d'itérateur de boucle utilisées. Enfin, nous (développeurs) avons pu expliquer humainement à l'utilisateur (développeur) pourquoi le code n'est pas collecté à l'aide de static_assert , ainsi que enable_if , qui sélectionne désormais les surcharges nécessaires comme par magie.

Dans la cour était 2017! Déjà C ++ 17 était activement introduit dans GCC, clang, Visual Studio, partout où il y avait decltype (depuis C ++ 11), constexpr (depuis C ++ 11, mais significativement amélioré), les modules étaient presque en route, il y avait un bon moment. J'étais au travail et avec une certaine désapprobation, j'ai regardé la prochaine erreur de compilation interne dans mon Borland C ++ Builder 6.0, ainsi que de nombreuses erreurs de construction avec la prochaine version de la bibliothèque de boost. Je pense que vous comprenez maintenant d'où vient cette envie de construire des vélos. Nous avons utilisé Borland C ++ Builder 6.0 et Visual Studio 2010 pour Windows, g ++ version 4.4.2 ou inférieure pour QNX et pour certains systèmes Unix. Nous avons été épargnés de MacOS, ce qui était sans aucun doute un avantage. Aucun autre compilateur (y compris C ++ 11) n'a même pu être envisagé pour des raisons que nous laissons en dehors de cet article.

"Et ce qui pourrait être si compliqué là-bas" - une pensée s'est glissée dans mes tentatives épuisées de faire un boost sous le bon vieux cerveau de constructeur. "Tout ce dont j'ai besoin est de type_traits , thread , mutex , peut-être chrono , nullptr serait bien." J'ai raisonné et je me suis mis au travail.

Chapitre 1. Viam supervadet vadens


Il était nécessaire de commencer par où et par où - naturellement j'avais un certain nombre de fichiers d'en-tête et de codes source dispersés à travers les projets avec des implémentations de fonctionnalités similaires ou identiques de la bibliothèque standard C ++ 11 standard de mon développement, ainsi que honnêtement empruntés ou traités à partir des codes de ce même gcc et boost. En combinant tout cela, j'ai eu un désordre de fonctions, de classes, de macros qui était censé se transformer en une bibliothèque standard élégante et élancée. Ayant estimé la quantité de travail, j'ai immédiatement décidé d'abandonner l'implémentation de tout et de tout, me limitant au développement d'un «add-on» sur la bibliothèque C ++ 98 standard fournie avec le compilateur.

Dans la version initiale, il n'y avait pas d'adhésion particulière à la norme, principalement les problèmes appliqués ont été résolus. Par exemple, nullptr ressemblait à ceci:

#define nullptr 0 

static_assert a également été résolu simplement:

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

std :: to_string a été implémenté via std :: stringstream , qui a été remplacé par std :: strstream dans les implémentations sans le fichier d'en-tête sstream , et tout cela a été immédiatement inséré dans l' espace de noms std :

  #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(); } } 

Il y avait aussi des «trucs» qui n'étaient pas inclus dans la norme, mais néanmoins utiles dans le travail de tous les jours, comme les macros forever ou countof :

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

countof est ensuite transformé en une option plus C ++:

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

Le travail avec les threads (le thread du fichier d'en-tête de std) a été implémenté via certaines des bibliothèques Tiny, réécrites en tenant compte des fonctionnalités de l'ensemble du zoo des compilateurs et du système d'exploitation. Et peut-être que type_traits était dans une certaine mesure déjà similaire à ce que la norme C ++ 11. exigeait : std :: enable_if , std :: integrale_constant , std :: is_const et les modèles similaires déjà utilisés dans le développement.

  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 { }; } // ...     

Il a été décidé de séparer toutes les macros, fonctions et types non standard et "compilateur" dans un fichier d'en-tête core.h. Et, contrairement à la pratique du boost, où la "commutation" d'implémentations utilisant des macros est largement utilisée, abandonner les macros liées aux choses dépendantes du compilateur dans tous les fichiers de bibliothèque, sauf core.h. De plus, la fonctionnalité qui ne peut pas être implémentée sans l'utilisation de «hacks» (violation de la norme, s'appuyant sur un comportement non défini pour être quelque peu défini), ou est implémentée individuellement pour chaque compilateur (par le biais de ses macros intégrées, par exemple), il a été décidé de ne pas l'ajouter à la bibliothèque, afin de ne pas produire un autre boost monstrueux (mais beau). Par conséquent, la principale et pratiquement la seule chose pour laquelle core.h est utilisé est de déterminer s'il existe une prise en charge pour nullptr intégré (car les compilateurs jurent en cas de substitution des mots réservés), la prise en charge de static_assert intégré (encore une fois, pour éviter de croiser un mot réservé) et la prise en charge des types intégrés C ++ 11 char16_t et char32_t .

Pour l'avenir, je peux dire que l'idée a été presque un succès, car la plupart de ce qui est défini dans boost par des macros dures en fonction d'un compilateur particulier, dans cette implémentation est déterminé par le compilateur lui-même au stade de la compilation.

La fin du premier chapitre. Dans le deuxième chapitre, je continuerai la narration sur les difficultés de traiter avec les compilateurs, sur les béquilles trouvées et les solutions élégantes dans les entrailles de gcc, boost et Visual Studio, ainsi qu'une description de mes impressions de ce que j'ai vu et acquis de l'expérience avec des exemples de code.

Merci de votre attention.

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


All Articles