C++ Rússia: como aconteceu

Se no início da peça você disser que há código C++ pendurado na parede, no final ele certamente dará um tiro no seu pé.

Bjarne Stroustrup

De 31 de outubro a 1º de novembro, a conferência C++ Russia Piter foi realizada em São Petersburgo - uma das conferências de programação de grande escala na Rússia, organizada pelo JUG Ru Group. Os palestrantes convidados incluem membros do Comitê de Padrões C++, palestrantes da CppCon, autores de livros da O'Reilly e mantenedores de projetos como LLVM, libc++ e Boost. A conferência é destinada a desenvolvedores C++ experientes que desejam aprofundar seus conhecimentos e trocar experiências em comunicação ao vivo. Estudantes, estudantes de pós-graduação e professores universitários recebem descontos muito bons.

A edição de Moscou da conferência estará disponível para visita já em abril do próximo ano, mas enquanto isso nossos alunos contarão as coisas interessantes que aprenderam no último evento. 

C++ Rússia: como aconteceu

Fotos de álbum de conferência

Sobre nós

Dois alunos da Escola Superior de Economia da National Research University - São Petersburgo trabalharam neste cargo:

  • Liza Vasilenko é uma estudante do 4º ano de graduação que estuda Linguagens de Programação como parte do programa de Matemática Aplicada e Ciência da Computação. Tendo me familiarizado com a linguagem C++ em meu primeiro ano na universidade, posteriormente ganhei experiência trabalhando com ela por meio de estágios na indústria. Minha paixão pelas linguagens de programação em geral e pela programação funcional em particular deixou sua marca na seleção dos relatórios da conferência.
  • Danya Smirnov é aluna do 1º ano do programa de mestrado “Programação e Análise de Dados”. Ainda na escola, escrevi problemas de Olimpíadas em C++ e, de alguma forma, aconteceu que a linguagem surgiu constantemente nas atividades educacionais e acabou se tornando a principal linguagem de trabalho. Decidi participar da conferência para aprimorar meus conhecimentos e também conhecer novas oportunidades.

No boletim informativo, a liderança do corpo docente costuma 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

  • Доклады

Ao longo de dois dias, especialistas leram 30 relatórios, cobrindo muitos tópicos importantes: usos engenhosos de recursos da linguagem para resolver problemas aplicados, futuras atualizações da linguagem em conexão com o novo padrão, compromissos no design do C++ e precauções ao trabalhar com suas consequências, exemplos de arquitetura de projeto interessante, bem como alguns detalhes subjacentes da infraestrutura linguística. Três apresentações ocorreram simultaneamente, geralmente duas em russo e uma em inglês.

  • Zonas de discussão

Após a palestra, todas as perguntas não feitas e discussões inacabadas foram transferidas para áreas especialmente designadas para comunicação com os palestrantes, equipadas com marcadores. Uma boa forma de passar o intervalo entre as falas com uma conversa agradável.

  • Lightning Talks e discussões informais

Se quiser fazer um breve relatório, você pode se inscrever no quadro branco para o Lightning Talk noturno e ter cinco minutos para falar sobre qualquer coisa sobre o tópico da conferência. Por exemplo, uma rápida introdução aos desinfetantes para C++ (para alguns era novo) ou uma história sobre um bug na geração de onda senoidal que só pode ser ouvido, mas não visto.

Outro formato é o painel de discussão “Comitê de coração para coração”. No palco estão alguns membros do comitê de padronização, no projetor há uma lareira (oficialmente - para criar uma atmosfera sincera, mas o motivo “porque TUDO ESTÁ PEGANDO” parece mais engraçado), perguntas sobre o padrão e a visão geral do C++ , sem discussões técnicas acaloradas e holiwars. Descobriu-se que o comitê também contém pessoas vivas que podem não ter certeza absoluta de algo ou não saber de algo.

Para os fãs de holivares, o terceiro evento permaneceu em questão - a sessão BOF “Go vs. C++”. Pegamos um amante de Go, um amante de C++, antes do início da sessão eles preparam juntos 100500 slides sobre um tópico (como problemas com pacotes em C++ ou falta de genéricos em Go), e então eles têm uma discussão animada entre si e com o público, e o público tenta entender dois pontos de vista ao mesmo tempo. Se um holivar começar fora de contexto, o moderador intervém e reconcilia as partes. Este formato é viciante: várias horas após o início, apenas metade dos slides foram concluídos. O fim teve que ser bastante acelerado.

  • Estandes de parceiros

Os parceiros da conferência estiveram representados nos salões - nos estandes falaram sobre projetos atuais, ofereceram estágios e empregos, realizaram quizzes e pequenos concursos, além de sortearem lindos prêmios. Ao mesmo tempo, algumas empresas chegaram a se oferecer para passar pelas etapas iniciais das entrevistas, o que poderia ser útil para quem veio não apenas para ouvir reportagens.

Detalhes técnicos dos relatórios

Ouvimos relatórios nos dois dias. Às vezes era difícil escolher um relatório entre os paralelos - concordamos em nos separar e trocar os conhecimentos adquiridos nos intervalos. E mesmo assim, parece que muita coisa ficou de fora. Gostaríamos aqui de falar sobre o conteúdo de alguns dos relatórios que achamos mais interessantes

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

C++ Rússia: como aconteceu
Deslizar de презентации

Como o título sugere, Roman procurou trabalhar com exceções usando o LLVM como exemplo. Ao mesmo tempo, para quem não utiliza Clang em seu trabalho, o relatório ainda pode dar uma ideia de como o código poderia ser potencialmente otimizado. Isto ocorre porque os desenvolvedores de compiladores e bibliotecas padrão correspondentes se comunicam entre si e muitas soluções bem-sucedidas podem coincidir.

Portanto, para lidar com uma exceção, você precisa fazer muitas coisas: chamar o código de tratamento (se houver) ou liberar recursos no nível atual e aumentar a pilha. Tudo isso leva ao fato de que o compilador adiciona instruções adicionais para chamadas que potencialmente lançam exceções. Portanto, se a exceção não for realmente levantada, o programa ainda executará ações desnecessárias. Para reduzir de alguma forma o overhead, o LLVM possui diversas heurísticas para determinar situações onde o código de tratamento de exceções não precisa ser adicionado ou o número de instruções “extras” pode ser reduzido.

O palestrante examina cerca de uma dúzia deles e mostra situações em que 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 os alunos à conclusão de que o código contendo tratamento de exceções nem sempre pode ser executado com sobrecarga zero e dá o seguinte conselho:

  • no desenvolvimento de bibliotecas, vale a pena abandonar as exceções de princípio;
  • se exceções ainda forem necessárias, sempre que possível, vale a pena adicionar modificadores noexcept (e const) em todos os lugares para que o compilador possa otimizar o máximo possível.

Em geral, o orador confirmou a opinião de que é melhor utilizar as excepções ao mínimo ou abandoná-las completamente.

Os slides do relatório estão disponíveis no seguinte link: [“Exceções C++ através das lentes das otimizações do compilador LLVM”]

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

C++ Rússia: como aconteceu
Deslizar de презентации

Um dos muitos relatórios desta conferência dedicados às inovações em C++20 foi memorável não apenas pela sua apresentação colorida, mas também pela sua clara identificação dos problemas existentes com a lógica de processamento da coleção (for loop, callbacks).

Adi Shavit destaca o seguinte: os métodos atualmente disponíveis percorrem toda a coleção e não fornecem acesso a algum estado intermediário interno (ou fornecem no caso de callbacks, mas com um grande número de efeitos colaterais desagradáveis, como Callback Hell) . Parece que existem iteradores, mas mesmo com eles nem tudo é tão tranquilo: não há pontos comuns de entrada e saída (início → fim versus rbegin → rend e assim por diante), não está claro por quanto tempo iremos iterar? A partir do C++20, esses problemas estão resolvidos!

Primeira opção: intervalos. Ao agrupar iteradores, obtemos uma interface comum para o início e o fim de uma iteração e também obtemos a capacidade de compor. Tudo isso facilita a construção de pipelines completos de processamento de dados. Mas nem tudo é tão tranquilo: parte da lógica de cálculo está localizada dentro da implementação de um iterador específico, o que pode complicar a compreensão e a depuração do código.

C++ Rússia: como aconteceu
Deslizar de презентации

Bem, para este caso, o C++20 adicionou corrotinas (funções cujo comportamento é semelhante aos geradores em Python): a execução pode ser adiada retornando algum valor atual enquanto preserva um estado intermediário. Assim, conseguimos não apenas trabalhar com os dados como eles aparecem, mas também encapsular toda a lógica dentro de uma corrotina específica.

Mas há um problema: no momento eles são apenas parcialmente suportados pelos compiladores existentes e também não são implementados tão bem quanto gostaríamos: por exemplo, ainda não vale a pena usar referências e objetos temporários em corrotinas. Além disso, existem algumas restrições sobre o que podem ser corrotinas, e funções constexpr, construtores/destruidores e main não estão incluídos nesta lista.

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

Materiais:

Truques C++ de Yandex.Taxi, Anton Polukhin

Nas minhas atividades profissionais, às vezes tenho que implementar coisas puramente auxiliares: um wrapper entre a interface interna e a API de alguma biblioteca, logging ou parsing. Nesse caso, geralmente não há necessidade de 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 de logs por hora! Então cada milissegundo conta e, portanto, você terá que 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 quero me livrar dos arquivos de cabeçalho de bibliotecas externas - isso compilará mais rapidamente e você poderá se proteger de possíveis conflitos de nomes e outros erros semelhantes. 

Ok, movemos #include para o arquivo .cpp: precisamos de uma declaração direta da API empacotada, bem como std::unique_ptr. Agora temos alocações dinâmicas e outras coisas desagradáveis, como dados espalhados por um monte de dados e garantias reduzidas. std::aligned_storage pode ajudar com tudo isso. 

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 wrapper - vamos fazer nosso modelo pimpl com parâmetros , use alguns valores arbitrários e adicione uma verificação ao destruidor de que acertamos 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á está definido durante o processamento do destruidor, esse código será analisado corretamente e na fase de compilação produzirá o tamanho necessário e os valores de alinhamento que precisam ser inseridos como erros. Assim, ao custo de uma execução de compilação adicional, 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 pareceram menos impressionantes e, portanto, não serão mencionados nesta revisão.

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

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

Nesta palestra, Björn Fahller mostra diversas maneiras diferentes de combater a falha estilística das verificações repetidas de condições:

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

Soa familiar? Usando várias técnicas poderosas de C++ introduzidas em padrões recentes, você pode implementar com elegância a mesma funcionalidade sem qualquer perda de desempenho. Comparar:   

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

Para lidar com um número não fixo de verificações, você precisa imediatamente usar modelos variados e expressões de dobra. Vamos supor que queremos verificar a igualdade de diversas variáveis ​​com o elemento state_type do enum. A primeira coisa que vem à mente é escrever uma 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) || ...); 
}

Este resultado intermédio é decepcionante. Até agora o código não está se tornando mais legível:

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

Parâmetros de modelo sem tipo ajudarão a melhorar um pouco a situação. Com a ajuda deles, transferiremos os elementos enumeráveis ​​do enum 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)); 

Ao usar auto em um parâmetro de modelo sem tipo (C++17), a abordagem simplesmente generaliza 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 sem tipo:


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

Através destas melhorias sucessivas, a sintaxe fluente desejada para verificações é alcançada:


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 sugerir os parâmetros do modelo de estrutura desejados ao compilador, que conhece os tipos dos argumentos do construtor. 

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

No final, não se esqueça de dar um polimento:

  • Lembre-se de que lambdas são constexpr gratuitamente; 
  • Vamos adicionar o encaminhamento perfeito e observar sua sintaxe feia em relação ao pacote de parâmetros no fechamento lambda;
  • Vamos dar ao compilador mais oportunidades para otimizações com condicional noexcept; 
  • Vamos cuidar de uma saída de erro mais compreensível em modelos graças aos valores de retorno explícitos de lambdas. Isso forçará o compilador a fazer mais verificações antes que a função do modelo seja realmente chamada - no estágio de verificação de tipo. 

Para obter detalhes, consulte os materiais da palestra: 

Nossas impressões

Nossa primeira participação no C++ Rússia foi memorável pela intensidade. Tive a impressão do C++ Rússia como um evento sincero, onde a linha entre o treinamento e a comunicação ao vivo é quase imperceptí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, composto por relatórios, cobre uma ampla gama de tópicos, incluindo inovações em C++, estudos de caso de grandes projetos e considerações arquitetônicas ideológicas. Mas seria injusto ignorar o componente social do evento, que ajuda a superar as barreiras linguísticas em relação não apenas ao C++.

Agradecemos aos organizadores da conferência pela oportunidade de participar de tal evento!
Você deve ter visto a postagem dos organizadores sobre o passado, presente e futuro do C++ Rússia no blog JUG Ru.

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

Fonte: habr.com

Adicionar um comentário