
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 vadensCapítulo 2. #ifndef __CPP11_SUPPORT__ #define __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endifCapítulo 3. Localizando a Implementação NULLPTR PerfeitaCapí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 modelosCapí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(;;)
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];
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; };
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.