Como eu escrevi a biblioteca C ++ 11 padrão ou por que o impulso é tão assustador? 1. Introdução

Sim - sim, com esse lema, corri para a batalha.

Em vez do prefácio


Talvez com esta imagem qualquer história sobre boost , Loki , independente e também implementações da biblioteca C ++ padrão fornecida com compiladores, deva começar.

Sim, sim, e se você pensou que os desenvolvedores da biblioteca padrão para o mesmo g ++, clang, Visual Studio ou, Deus me perdoe, C ++ Builder (anteriormente Borland, mas atualmente o Embarcadero) são gurus que não fazem muletas, eles não quebram o padrão do compilador e não escreva bicicletas, é mais provável que você não esteja usando tão ativamente a biblioteca C ++ padrão como pensava.

O artigo está escrito como uma história e contém muita "água" e digressões, mas espero que minha experiência e o código resultante sejam úteis para aqueles que enfrentaram problemas semelhantes ao desenvolver em C ++, especialmente em compiladores mais antigos. Link para o GitHub com o resultado de hoje para impacientes e não leitores:

https://github.com/oktonion/stdex (confirmações e críticas construtivas são bem-vindas)

E agora, as primeiras coisas primeiro.


Sumário


1. Introdução
Capítulo 1. Viam supervadet vadens
Capítulo 2. #ifndef __CPP11_SUPPORT__ #define __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endif
Capítulo 3. Localizando a Implementação NULLPTR Perfeita
Capítulo 4. C ++ Template Magic
.... 4.1 Começamos pequenos
.... 4.2 Sobre quantos erros milagrosos o log compila para nós
.... 4.3 Ponteiros e tudo-tudo-tudo
.... 4.4 O que mais é necessário para a biblioteca de modelos
Capítulo 5
...

Entrada


Em 2017, o C ++ 11 há muito tempo explode em um novo fluxo de compiladores novos e relativamente novos, trazendo trabalho padronizado com fluxos, mutexes, expandindo a programação de modelos e padronizando abordagens para ele; existem tipos “grandes” e longos no padrão , finalmente se livrou da necessidade generalizada de exibir tipos para o compilador usando auto (adeus std :: map <tipo, tipo> :: const_iterator it = ... - bem, você me entende), e a combinação desse recurso com o novo de cada um se tornou um dos mais comuns implementações de iterador de loop usadas. Finalmente, nós (desenvolvedores) fomos capazes de dizer humanamente ao usuário (desenvolvedor) por que o código não é coletado usando static_assert , bem como enable_if , que agora seleciona as sobrecargas necessárias, como por mágica.

No quintal era 2017! Já o C ++ 17 foi introduzido ativamente no GCC, clang, Visual Studio, em todos os lugares havia dectype (desde C ++ 11), constexpr (desde C ++ 11, mas melhorou significativamente), os módulos estavam quase a caminho, houve um bom tempo. Eu estava no trabalho e, com alguma desaprovação, observei o próximo Erro interno do compilador no meu Borland C ++ Builder 6.0, bem como muitos erros de compilação na próxima versão da biblioteca de reforço. Acho que agora você entende de onde veio esse desejo de construir bicicletas. Usamos o Borland C ++ Builder 6.0 e o Visual Studio 2010 para Windows, g ++ versão 4.4.2 ou inferior para QNX e para alguns sistemas unix. Fomos poupados do MacOS, o que sem dúvida foi uma vantagem. Nenhum outro compilador (incluindo o C ++ 11) pode ser considerado por motivos que deixamos de fora deste artigo.

"E o que poderia ser tão complicado por lá" - um pensamento surgiu em minhas tentativas exaustas de impulsionar o cérebro do bom e velho construtor. "Tudo o que preciso é type_traits , thread , mutex , talvez chrono , nullptr seria bom." Eu raciocinei e comecei a trabalhar.

Capítulo 1. Viam supervadet vadens


Era necessário começar de onde e de onde - naturalmente, eu tinha vários arquivos de cabeçalho e códigos-fonte espalhados por projetos com implementações de funcionalidade semelhante ou idêntica da biblioteca padrão C ++ 11 padrão do meu desenvolvimento, bem como honestamente emprestado ou processado a partir dos códigos desse código. mesmo gcc e impulso. Combinando tudo isso, obtive algumas bagunças de funções, classes e macros que deveriam se transformar em uma biblioteca padrão elegante e esbelta. Tendo estimado a quantidade de trabalho, decidi imediatamente abandonar a implementação de tudo e de tudo, limitando-me ao desenvolvimento de um “complemento” sobre a biblioteca C ++ 98 padrão que acompanha o compilador.

Na versão inicial, não havia aderência específica ao padrão, principalmente os problemas aplicados foram resolvidos. Por exemplo, nullptr ficou assim:

#define nullptr 0 

static_assert também foi resolvido simplesmente:

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

std :: to_string foi implementado por meio de std :: stringstream , que foi substituído por std :: strstream em implementações sem o arquivo de cabeçalho sstream , e tudo isso foi empurrado imediatamente para o namespace 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(); } } 

Havia também "truques" que não foram incluídos no padrão, mas, no entanto, são úteis no trabalho cotidiano, como a eternidade ou a contagem de macros :

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

countof então transformado em uma opção mais 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)) 

O trabalho com threads (o arquivo de cabeçalho std) foi implementado através de algumas das bibliotecas minúsculas, reescritas levando em conta os recursos de todo o zoológico de compiladores e do sistema operacional. E talvez type_traits até certo ponto já fosse semelhante ao exigido pelo padrão C ++ 11. Havia std :: enable_if , std :: integral_constant , std :: is_const e modelos similares que já eram usados ​​no desenvolvimento.

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

Foi decidido separar todas as macros, funções e tipos não padrão e "compiladores" em um arquivo de cabeçalho separado core.h. E, contrariamente à prática de boost, onde a "alternância" de implementações usando macros é amplamente usada, para abandonar macros relacionadas a itens dependentes do compilador em todos os arquivos de biblioteca, exceto core.h. Além disso, a funcionalidade que não pode ser implementada sem o uso de "hacks" (violação do padrão, dependendo do comportamento indefinido para ser um pouco definido) ou é implementada individualmente para cada compilador (por meio de suas macros incorporadas, por exemplo), foi decidido não adicionar à biblioteca, para não produzir outro impulso monstruoso (mas bonito). Como resultado, a principal e praticamente a única coisa para a qual core.h é usado é determinar se há suporte para nullptr interno (porque os compiladores juram que substituem palavras reservadas), suporte para static_assert interno (novamente, para evitar a interseção de uma palavra reservada) e suporte para tipos internos de C ++ 11 char16_t e char32_t .

Olhando para o futuro, posso dizer que a ideia foi quase um sucesso, porque a maior parte do que é definido no impulso por macros rígidas, dependendo de um compilador específico, nesta implementação é determinada pelo próprio compilador no estágio de compilação.

O fim do primeiro capítulo. No segundo capítulo, continuarei a narração sobre as dificuldades de lidar com compiladores, sobre muletas encontradas e soluções elegantes nas entranhas do gcc, boost e Visual Studio, bem como uma descrição das minhas impressões sobre o que vi e ganhei experiência com exemplos de código.

Obrigado pela atenção.

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


All Articles