¿Cómo escribí la biblioteca estándar de C ++ 11 o por qué boost es tan aterrador? Introduccion

Sí, sí, con este lema me apresuré a la batalla.

En lugar del prólogo


Quizás con esta imagen debería comenzar cualquier historia sobre boost , Loki , independiente y también implementaciones de la biblioteca estándar de C ++ suministrada con compiladores.

Sí, sí, y si pensabas que los desarrolladores de la biblioteca estándar para el mismo g ++, clang, Visual Studio o, Dios me perdone, C ++ Builder (anteriormente Borland, pero el actual Embarcadero) son gurús que no hacen muletas, no rompen el estándar para su compilador y no escriba bicicletas, lo más probable es que no esté usando la biblioteca C ++ estándar tan activamente como pensaba.

El artículo está escrito como una historia y contiene mucha "agua" y digresiones, pero espero que mi experiencia y el código resultante sean útiles para aquellos que enfrentaron problemas similares al desarrollar en C ++, especialmente en compiladores más antiguos. Enlace a GitHub con el resultado de hoy para impacientes y no lectores:

https://github.com/oktonion/stdex (se aceptan confirmaciones y críticas constructivas)

Y ahora, lo primero es lo primero.


Tabla de contenidos


Introduccion
Capítulo 1. Viam supervadet vadens
Capítulo 2. #ifndef __CPP11_SUPPORT__ #define __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endif
Capítulo 3. Encontrar la implementación nullptr perfecta
Capítulo 4. Magia de plantilla de C ++
.... 4.1 Comenzamos pequeño
.... 4.2 Acerca de cuántos errores milagrosos compila el registro para nosotros
.... 4.3 Punteros y todo-todo-todo
.... 4.4 ¿Qué más se necesita para la biblioteca de plantillas?
Capitulo 5
...

Entrada


Era 2017, C ++ 11 ha estallado en un nuevo flujo de compiladores nuevos y relativamente nuevos, trayendo trabajo estandarizado con flujos, mutexes, ampliando la programación de plantillas y estandarizando enfoques, hay tipos largos largos "grandes" en el estándar , finalmente se deshizo de la necesidad generalizada de mostrar tipos para el compilador usando auto (goodbye std :: map <type, type> :: const_iterator it = ... - bueno, me entiendes), y la combinación de esta función con la nueva para cada uno se ha convertido en una de las más comunes implementaciones de iterador de bucle utilizado. Finalmente, nosotros (los desarrolladores) pudimos decirle humanamente al usuario (desarrollador) por qué el código no se está construyendo usando static_assert , así como enable_if , que ahora selecciona las sobrecargas necesarias como por arte de magia.

En el patio fue 2017! C ++ 17 ya se introdujo activamente en GCC, clang, Visual Studio, en todas partes había decltype (desde C ++ 11), constexpr (desde C ++ 11, pero mejoró significativamente), los módulos estaban casi en camino, hubo un buen momento. Estaba en el trabajo y, con cierta desaprobación, miré el siguiente error interno del compilador en mi Borland C ++ Builder 6.0, así como muchos errores de compilación con la próxima versión de la biblioteca de impulso. Creo que ahora entiendes de dónde vino este deseo de construir bicicletas. Utilizamos Borland C ++ Builder 6.0 y Visual Studio 2010 para Windows, g ++ versión 4.4.2 o inferior para QNX y para algunos sistemas unix. Nos ahorramos MacOS, lo que sin duda fue una ventaja. Ningún otro compilador (incluido C ++ 11) podría considerarse por razones que dejamos fuera de este artículo.

"Y qué podría ser tan complicado allí", un pensamiento se deslizó en mis intentos exhaustivos de impulsar el buen cerebro del viejo constructor. "Todo lo que necesito es type_traits , thread , mutex , quizás chrono , nullptr sería bueno". Razoné y me puse a trabajar.

Capítulo 1. Viam supervadet vadens


Era necesario comenzar desde dónde y comenzar desde dónde, naturalmente, tenía una cantidad de archivos de encabezado y códigos fuente dispersos en los proyectos con implementaciones de funcionalidad similar o idéntica de la biblioteca estándar C ++ 11 estándar de mi desarrollo, así como prestado o procesado honestamente a partir de los códigos de ese mismo gcc y boost. Combinando todo esto, obtuve un montón de funciones, clases, macros que se suponía que se convertiría en una biblioteca estándar elegante y esbelta. Habiendo estimado la cantidad de trabajo, inmediatamente decidí abandonar la implementación de todo y todo, limitándome al desarrollo de un "complemento" sobre la biblioteca estándar de C ++ 98 que viene con el compilador.

En la versión inicial no había una adherencia particular al estándar, principalmente se resolvieron los problemas aplicados. Por ejemplo, nullptr se veía así:

#define nullptr 0 

static_assert también se resolvió simplemente:

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

std :: to_string se implementó a través de std :: stringstream , que fue reemplazado por std :: strstream en implementaciones sin el archivo de encabezado sstream , y todo esto se introdujo inmediatamente en el espacio de nombres 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(); } } 

También había "trucos" que no estaban incluidos en el estándar, pero que, sin embargo, eran útiles en el trabajo diario, como las macros forever o countof :

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

countof luego se transformó en una opción más 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)) 

El trabajo con subprocesos (el subproceso del archivo de encabezado de std) se implementó a través de algunas de las bibliotecas de Tiny, reescrito teniendo en cuenta las características de todo el zoológico de compiladores y el sistema operativo. Y quizás type_traits en cierta medida ya era similar a lo que requería el estándar C ++ 11. Había std :: enable_if , std :: integral_constant , std :: is_const y las plantillas similares que ya se usaban en el desarrollo.

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

Se decidió separar todas las macros, funciones y tipos no estándar y "compiladores" en un archivo de encabezado separado core.h. Y, contrariamente a la práctica de boost, donde se utilizan ampliamente las implementaciones de "cambio" utilizando macros, para abandonar las macros relacionadas con elementos dependientes del compilador en todos los archivos de la biblioteca, excepto core.h. Además, la funcionalidad que no se puede implementar sin el uso de "hacks" (violación del estándar, confiando en que el comportamiento indefinido esté algo definido), o se implementa individualmente para cada compilador (a través de sus macros incorporadas, por ejemplo), se decidió no agregar a la biblioteca, para no producir otro impulso monstruoso (pero hermoso). Como resultado, lo principal y prácticamente lo único para lo que se usa core.h es determinar si hay soporte para nullptr incorporado (porque los compiladores juran si anulan las palabras reservadas), soporte para static_assert incorporado (nuevamente, para evitar la intersección de una palabra reservada) y soporte para tipos incorporados C ++ 11 char16_t y char32_t .

Mirando hacia el futuro, puedo decir que la idea fue casi un éxito, porque La mayor parte de lo que se define en el impulso por macros duras dependiendo de un compilador particular, en esta implementación, está determinado por el compilador en la etapa de compilación.

El final del primer capítulo. En el segundo capítulo, continuaré la narración sobre las dificultades de tratar con compiladores, sobre muletas encontradas y soluciones elegantes en las entrañas de gcc, boost y Visual Studio, así como una descripción de mis impresiones de lo que vi y obtuve experiencia con ejemplos de código.

Gracias por su atencion

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


All Articles