C ++ Rússia: como foi

Se, no início da peça, você diz que o código C ++ está pendurado na parede, então, no final, ele certamente deve lhe dar um tiro no pé.

Bjarne Stroustrup

De 31 de outubro a 1 de novembro, São Petersburgo sediou a C ++ Russia Piter Conference, uma das maiores conferências de programação na Rússia, organizada pelo JUG Ru Group. Entre os palestrantes convidados estão membros do comitê de padronização do C ++, palestrantes do CppCon, autores de livros de O'Reilly, além de mantenedores de projetos como LLVM, libc ++ e Boost. A conferência é destinada a desenvolvedores experientes em C ++ que desejam aprofundar seus conhecimentos e trocar experiências em comunicação ao vivo. Estudantes, estudantes de graduação e professores universitários recebem descontos muito agradáveis.

A edição de Moscou da conferência já pode ser visitada em abril do próximo ano, mas, por enquanto, nossos alunos contarão as coisas interessantes que aprenderam no último evento.



Foto do álbum da conferência

Sobre nós


Dois alunos da Escola Superior de Economia - São Petersburgo, trabalharam neste post:

  • Lisa Vasilenko é uma aluna do 4º ano que estuda a direção de "Linguagens de Programação" como parte do programa "Matemática Aplicada e Ciência da Computação". Familiarizado com a linguagem C ++ no primeiro ano da universidade, posteriormente adquiriu experiência trabalhando com ela em estágios na indústria. A paixão pelas linguagens de programação em geral e pela programação funcional em particular deixou sua marca na escolha dos relatórios na conferência.
  • Danya Smirnov é uma aluna do primeiro ano do programa de mestrado “Programação e Análise de Dados”. Enquanto ainda estava na escola, ele escreveu problemas das Olimpíadas em C ++ e, de alguma forma, aconteceu que o idioma aparecia constantemente nas atividades educacionais e, como resultado, tornou-se o principal trabalho. Decidi participar da conferência para aprimorar meu conhecimento e aprender sobre novas oportunidades.

No boletim, os líderes do corpo docente costumam compartilhar informações sobre eventos educacionais relacionados à nossa especialidade. Em setembro, vimos informações sobre C ++ Rússia e decidimos nos registrar como ouvintes. Esta é a nossa primeira experiência de participação em tais conferências.

Estrutura da Conferência


  • Relatórios


Ao longo de dois dias, os especialistas leram 30 relatórios destacando muitos tópicos importantes: aplicativos espirituosos de recursos de linguagem para resolver problemas aplicados, atualizações futuras de idiomas devido ao novo padrão, comprometimentos no design e precauções em C ++ ao trabalhar com suas consequências, exemplos de arquitetura de projeto interessante, bem como algumas partes do mecanismo da infraestrutura de idiomas. Ao mesmo tempo, ocorreram três apresentações, na maioria das vezes duas em russo e uma em inglês.

  • Zonas de discussão


Após o discurso, todas as perguntas não respondidas e discussões incompletas foram transferidas para áreas de comunicação especialmente designadas com alto-falantes equipados com placas de identificação. Uma boa maneira de passar o intervalo entre as apresentações para uma conversa agradável.

  • Lightning Talks e discussões informais


Se você quiser fazer um breve relatório, poderá se inscrever para uma Lightning Talk à noite em um quadro de marcadores e ter cinco minutos para conversar sobre qualquer assunto sobre o tópico da conferência. Por exemplo, uma rápida introdução aos desinfetantes para C ++ (que acabou sendo nova para alguns) ou uma história sobre um bug na geração de um sinusóide que você só pode ouvir, mas não vê.

Outro formato é o painel de discussão “Com o Comitê da Alma”. No palco, há alguns membros do comitê de padronização; no projetor, há uma lareira (oficialmente - para criar uma atmosfera de alma, mas a razão "porque TUDO EM FOGO" parece mais engraçado), as perguntas são sobre a visão geral e padrão do C ++, sem discussões técnicas e holivars acalorados. Verificou-se que as pessoas vivas também fazem parte do comitê, que pode não estar completamente certo de algo ou pode não saber de nada.

Para os entusiastas do holivar, o terceiro evento permaneceu - a sessão do BOF "Go against C ++". Levamos um amante do Go, um amante do C ++, antes do início da sessão, eles preparam 100500 slides sobre o tópico juntos (como problemas com pacotes em C ++ ou falta de genéricos no Go) e depois discutem animadamente entre si e o público, e o público tenta entender dois pontos de vista ao mesmo tempo . Se o holivar não iniciar os negócios, o moderador intervém e reconcilia as partes. Esse formato é viciante: algumas horas após o início, apenas metade dos slides foi concluída. O fim teve que ser muito acelerado.

  • Stands de parceiros


Os parceiros da conferência estavam representados nos corredores - eles conversaram sobre projetos atuais nas estandes, ofereceram estágios e empregos, realizaram quizzes e pequenas competições e também tiveram ótimos prêmios. No entanto, algumas empresas até se ofereceram para passar pelas etapas iniciais das entrevistas, o que pode ser útil para aqueles que vieram não apenas ouvir os relatórios.

Detalhes técnicos dos relatórios


Ouvimos relatórios nos dois dias. Às vezes, era difícil escolher um relatório entre os que estavam sendo executados em paralelo - concordamos em compartilhar e trocar o conhecimento adquirido durante os intervalos. E mesmo assim, parece que muito se perdeu. Aqui gostaríamos de falar sobre o conteúdo de alguns relatórios que nos pareceram os mais interessantes

Exceções em C ++ através do prisma das otimizações do compilador, Roman Rusyaev




Slide de apresentação

Como o nome indica, Roman procurou trabalhar com exceções usando o LLVM como exemplo. Ao mesmo tempo, para aqueles que não usam o Clang em seus trabalhos, o relatório ainda pode dar uma idéia de como o código pode ser otimizado. Isso ocorre porque os desenvolvedores de compiladores e as bibliotecas padrão correspondentes se comunicam entre si e muitas soluções bem-sucedidas podem coincidir.

Portanto, para lidar com a exceção, é necessário executar várias ações: chame o código de processamento (se houver) ou libere recursos no nível atual e relaxe a pilha mais alto. Tudo isso leva ao fato de que o compilador adiciona instruções adicionais para lançar chamadas em potencial. Portanto, se uma exceção de fato não for causada, o programa ainda começará a executar ações desnecessárias. Para reduzir de alguma forma os custos indiretos, o LLVM possui várias heurísticas para determinar situações em que você não precisa adicionar um código de tratamento de exceção ou pode reduzir o número de instruções "desnecessárias".

O palestrante considera cerca de uma dúzia deles e mostra as situações em que eles ajudam a acelerar a execução do programa e aquelas em que esses métodos não são aplicáveis.

Assim, Roman Rusyaev leva o público à conclusão de que o código que contém o trabalho com exceções nem sempre pode ser executado com zero de sobrecarga e fornece as seguintes dicas:

  • ao desenvolver bibliotecas, você deve abandonar as exceções em princípio;
  • se você ainda precisar de exceções, sempre que possível, vale a pena adicionar modificadores noexcept (e const) para que o compilador possa otimizar o máximo possível.

Em geral, o orador reiterou a visão de que as exceções são melhor usadas ao mínimo, ou mesmo as abandonam.

Os slides do relatório estão disponíveis em: ["Exceções em C ++ através do prisma da otimização do compilador LLVM"]

Geradores, corotinas e outras doçuras que desenrolam o cérebro, Adi Shavit



Slide de apresentação

Um dos muitos relatórios desta conferência dedicados às inovações do C ++ 20 foi lembrado não apenas por sua apresentação colorida, mas também por sua clara designação dos problemas com a lógica de processamento de coleções (para loop de retorno de chamada).

Adi Shavit destaca o seguinte: os métodos atualmente disponíveis percorrem toda a coleção e não dão acesso a algum estado intermediário interno (ou no caso de retornos de chamada, mas com muitos efeitos colaterais desagradáveis, como o mesmo inferno de retorno de chamada). Parece que existem iteradores, mas nem tudo é tão fácil com eles: não há pontos de entrada e saída comuns (começo → fim versus rbegin → rend e assim por diante), não está claro quanto vamos iterar. Começando com C ++ 20, esses problemas foram resolvidos!

A primeira opção: intervalos. Devido ao wrapper na parte superior dos iteradores, obtemos uma interface comum para o início e o final da iteração, além da possibilidade de composição. Tudo isso facilita a construção de pipelines completos de processamento de dados. Mas nem tudo é tão tranquilo: parte da lógica dos cálculos está na implementação de um iterador específico, que pode complicar o código de percepção e depuração.


Slide de apresentação

Bem, neste caso, foram adicionadas corotinas no C ++ 20 (funções cujo comportamento é semelhante aos geradores em Python): a execução pode ser adiada retornando algum valor atual, mantendo o estado intermediário. Assim, conseguimos não apenas trabalhar com dados como eles aparecem, mas também encapsular toda a lógica dentro de uma determinada rotina.

Mas há uma mosca na pomada: no momento, eles são apenas parcialmente suportados pelos compiladores existentes e também não são implementados com a precisão que gostaríamos: por exemplo, links e objetos temporários não devem ser usados ​​em corotinas. Além disso, existem algumas restrições sobre o que podem ser corotinas, e as funções constexpr, construtores / destruidores e também as principais não estão incluídas nesta lista.

Assim, as corotinas resolvem uma parte significativa dos problemas com a simplicidade da lógica de processamento de dados, mas suas implementações atuais exigem refinamento.

Materiais:


Truques em C ++ de Yandex.Taxi, Anton Polukhin


Em sua atividade profissional, às vezes você precisa implementar coisas puramente auxiliares: um invólucro entre a interface interna e a API de alguma biblioteca, registrando ou analisando. No entanto, geralmente não há necessidade de qualquer otimização adicional. Mas e se esses componentes forem usados ​​em alguns dos serviços mais populares do Runet? Em tal situação, você terá que processar apenas terabytes por hora de logs! Cada milissegundo conta e, portanto, você precisa recorrer a vários truques - Anton Polukhin falou sobre eles.

Talvez o exemplo mais interessante tenha sido a implementação do padrão ponteiro para implementação (pimpl).

#include <third_party/json.hpp> //PROBLEMS! struct Value { Value() = default; Value(Value&& other) = default; Value& operator=(Value&& other) = default; ~Value() = default; std::size_t Size() const { return data_.size(); } private: third_party::Json data_; }; 

Neste exemplo, primeiro você deseja se livrar dos arquivos de cabeçalho das bibliotecas externas - eles serão compilados mais rapidamente e você pode se proteger de possíveis conflitos de nome e outros erros semelhantes.

Ok, movemos #include para o arquivo .cpp: você precisa da declaração de encaminhamento da API agrupada, além de std :: unique_ptr. Agora, temos alocações dinâmicas e outras coisas desagradáveis, como dados espalhados por uma pilha e garantias reduzidas. Com tudo isso, std :: align_storage pode ajudar.

 struct Value { // ... private: using JsonNative = third_party::Json; const JsonNative* Ptr() const noexcept; JsonNative* Ptr() noexcept; constexpr std::size_t kImplSize = 32; constexpr std::size_t kImplAlign = 8; std::aligned_storage_t<kImplSize, kImplAlign> data_; }; 

O único problema: você precisa especificar o tamanho e o alinhamento de cada invólucro - criaremos nosso modelo pimpl com os parâmetros <T, SizeT, AlignmentT>, usaremos alguns valores arbitrários e adicionaremos ao destruidor uma verificação para adivinhar tudo:

 ~FastPimpl() noexcept { validate<sizeof(T), alignof(T)>(); Ptr()->~T(); } template <std::size_t ActualSize, std::size_t ActualAlignment> static void validate() noexcept { static_assert( Size == ActualSize, "Size and sizeof(T) mismatch" ); static_assert( Alignment == ActualAlignment, "Alignment and alignof(T) mismatch" ); } 

Como T já foi determinado durante o processamento do destruidor, esse código será desmontado corretamente e, no estágio de compilação na forma de erros, exibirá os valores de tamanho e alinhamento necessários que precisam ser inseridos. Assim, ao custo de um início adicional de compilação, nos livramos da alocação dinâmica de classes agrupadas, ocultamos a API em um arquivo .cpp com a implementação e também obtemos um design mais adequado para armazenamento em cache pelo processador.

O registro e a análise pareciam menos impressionantes e, portanto, não serão mencionados nesta revisão.

Os slides do relatório estão disponíveis no link: ["C ++ Taxi Tricks"]

Técnicas modernas para manter seu código SECO, Björn Fahller


Nesta palestra, Björn Fahller mostra várias maneiras diferentes de lidar com falhas estilísticas, como repetidas verificações condicionais:

 assert(a == IDLE || a == CONNECTED || a == DISCONNECTED); 

Isso é familiar? Usando várias técnicas C ++ poderosas que apareceram nos padrões recentes, você pode implementar normalmente a mesma funcionalidade sem a menor perda de desempenho. Compare:

 assert(a == any_of(IDLE, CONNECTED, DISCONNECTED)); 

Para processar um número ilimitado de verificações, você é solicitado imediatamente a usar modelos variados e expressões de dobra. Suponha que desejamos verificar a igualdade de várias variáveis ​​com o elemento enum'a state_type. A primeira coisa que vem à mente é escrever a função auxiliar is_any_of:

 enum state_type { IDLE, CONNECTED, DISCONNECTED }; template <typename ... Ts> bool is_any_of(state_type s, const Ts& ... ts) { return ((s == ts) || ...); } 

Um resultado tão intermediário é decepcionante. Até agora, o código não está se tornando legível:

 assert(is_any_of(state, IDLE, DISCONNECTING, DISCONNECTED)); 

Parâmetros de modelo não-tipo ajudarão a melhorar um pouco a situação. Com a ajuda deles, transferimos os elementos enumerados para a lista de parâmetros do modelo:

 template <state_type ... states> bool is_any_of(state_type t) { return ((t == states) | ...); } assert(is_any_of<IDLE, DISCONNECTING, DISCONNECTED>(state)); 

Usando auto em um parâmetro de modelo não típico (C ++ 17), a abordagem é simplesmente generalizada para comparações não apenas com elementos state_type, mas também com tipos primitivos que podem ser usados ​​como parâmetros de modelo não-tipo:

 template <auto ... alternatives, typename T> bool is_any_of(const T& t) { return ((t == alternatives) | ...); } 

Por meio dessas melhorias incrementais, é alcançada a sintaxe superficial desejada para verificação:

 template <class ... Ts> struct any_of : private std::tuple<Ts ...> { //      tuple        using std::tuple<Ts ...>::tuple;        template <typename T>        bool operator ==(const T& t) const {                return std::apply(                        [&t](const auto& ... ts) {                                return ((ts == t) || ...);                        },                        static_cast<const std::tuple<Ts ...>&>(*this));        } }; template <class ... Ts> any_of(Ts ...) -> any_of<Ts ... >; assert(any_of(IDLE, DISCONNECTING, DISCONNECTED) == state); 

Neste exemplo, o guia de dedução serve para solicitar os parâmetros desejados da estrutura do modelo para um compilador que conhece os tipos de argumentos do construtor.

Mais interessante. Bjorn ensina a generalizar o código resultante para operadores de comparação, além de == e, em seguida, para operações arbitrárias. Juntamente com o exemplo de uso, são explicados recursos como o atributo no_unique_address (C ++ 20) e parâmetros de modelo nas funções lambda (C ++ 20). (Sim, agora a sintaxe lambda é ainda mais fácil de lembrar - são quatro pares consecutivos de colchetes de todos os tipos.) A solução final usando funções como partes construtoras realmente aquece minha alma, sem mencionar a expressão de tupla nas melhores tradições do cálculo lambda.

No final, não esqueça de colocar um gloss:

  • Lembre-se de que as lambdas são gratuitas;
  • Adicione encaminhamento perfeito e observe sua sintaxe feia aplicada ao pacote de parâmetros no fechamento lambda;
  • Vamos dar ao compilador mais opções para otimizações com exceção condicional;
  • Nós cuidaremos de uma saída de erro mais clara nos modelos devido aos valores de retorno explícitos das lambdas. Isso forçará o compilador a fazer mais verificações antes de realmente chamar a função de modelo - no estágio de verificação de tipo.

Para detalhes, consulte os materiais da palestra:


Nossas impressões


Nossa primeira participação no C ++ Russia foi lembrada por sua riqueza. Havia uma impressão de C ++ Rússia como um evento emocional, onde a linha entre aprendizado e comunicação ao vivo quase não é perceptível. Tudo, desde o humor dos palestrantes até as competições dos parceiros do evento, é propício para discussões acaloradas. O conteúdo da conferência, que consiste em relatórios, abrange uma ampla gama de tópicos, incluindo inovações em C ++, exemplos da prática de grandes projetos e considerações ideológicas da arquitetura. Mas seria injusto privar a atenção do componente social do evento, o que ajuda a superar barreiras linguísticas em relação não apenas ao C ++.

Agradecemos aos organizadores da conferência pela oportunidade de participar de um evento como esse!
Você pode ver a postagem dos organizadores sobre o passado, presente e futuro do C ++ Russia no blog JUG Ru .

Obrigado pela leitura e esperamos que nossa recontagem dos eventos tenha sido útil!

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


All Articles