A Lista de Boas e Más Travessuras do C++20 para Devs de Jogos

Os novos recursos do C++20 estão gerando reações mistas entre desenvolvedores de jogos, que acolhem facilidades como o operador de comparação de três vias, `std::bit_cast` e coroutines, mas se preocupam com tempos de compilação, inchaço de código e lacunas nas ferramentas. Formatação de strings com `{fmt}`/`std::format` e inicializadores designados ilustram a troca entre APIs mais seguras e expressivas e templates mais pesados ou regras de inicialização mais estritas, o que pesa muito em codebases AAA com 10M+ linhas. Alguns desenvolvedores estão refatorando para padrões parecidos com Rust ou considerando reescritas completas em Rust, argumentando que a segurança e a ergonomia podem superar os pequenos ganhos de desempenho ligados aos UB do C++ e à margem de otimização de baixo nível.

Operador de comparação de três vias <=>

  • Vários comentários explicam que definir <=> para um tipo gera automaticamente todos os seis operadores de comparação (<, >, <=, >=, ==, !=), reduzindo o boilerplate.
  • Isso simplifica a implementação para objetos semelhantes, já que apenas uma rotina precisa ser “ligada”.

std::format / {fmt}: desempenho, inchaço e tempos de compilação

  • Alguns se surpreendem ao ver fmt/format.h criticado; outros concordam que templates pesados e o estilo header-only prejudicam os tempos de compilação em grandes codebases C++20.
  • Um lado argumenta que o inchaço de tamanho de código é em grande parte mitigado por linkers modernos e por mover a lógica pesada para arquivos .cc; a preocupação maior é o tempo de compilação.
  • Os defensores de {fmt} dizem que ele é otimizado para velocidade de build, pode superar iostreams e é amplamente usado em formatação numérica e I/O críticas para desempenho.
  • Há confusão sobre por que o padrão sugerido de “despachar para uma TU não template” não poderia também ser usado com <format>.

Recursos do C++20 em grandes codebases de jogos

  • A discussão gira em torno de 10M+ LOC sendo realista para engines AAA (com exemplos como Unreal), e da dor de builds incrementais e, especialmente, dos tempos de linkedição.
  • Builds distribuídos e ferramentas como Incredibuild ajudam, mas os linkers (notadamente o da MSVC) continuam sendo um gargalo.
  • Alguns questionam por que alguém recompila mais do que alguns poucos arquivos; outros observam que mudanças em headers centrais ou flags do compilador disparam rebuilds enormes.

Inicializadores designados e semântica de C vs C++

  • Muitos não gostam do fato de que inicializadores designados em C++ precisam seguir a ordem de declaração, ao contrário de C.
  • As justificativas incluem:
    • A ordem de inicialização e destruição dos membros importa em C++.
    • Membros podem depender da inicialização de membros anteriores (por exemplo, int b = a + 1).
    • Permitir ordem arbitrária criaria bugs sutis ou exigiria quebrar regras existentes.
  • Alguns argumentam que structs POD “à la C” poderiam ser relaxadas, mas outros observam que isso seria frágil quando as structs mudassem.
  • Vários consideram o inicializador designado restrito do C++ ainda útil, embora menos poderoso que o do C99 (sem cadeias aninhadas, sem designadores de índice de array).

Rust vs C++ para engines de jogos e carregamento dinâmico

  • Um desenvolvedor fica tentado a reescrever uma engine de jogo em C++ em Rust por causa da ergonomia, mas teme impacto no cronograma, então está tornando o C++ “pronto para Rust” (ownership, design orientado a dados).
  • Há debate sobre Rust e bibliotecas dinâmicas:
    • Alguns afirmam que Rust “não permite” bibliotecas dinâmicas em geral; outros apontam que dylib existe, mas muitas vezes requer ABIs C para interoperabilidade.
    • Fluxos de trabalho de jogos como hot-reload de DLL com “C++ normal” são citados como uma grande vantagem; na prática, tanto C++ quanto Rust frequentemente recorrem a ABIs C para fronteiras estáveis de plugins.
    • Discute-se um modelo alternativo de plugins via processos separados e IPC, mas ele é visto como ordens de magnitude mais lento do que chamadas no mesmo processo.

UB de overflow com sinal e otimização

  • Um comentarista argumenta que o ganho de desempenho por manter overflow com sinal indefinido é pequeno (por exemplo, às vezes evitar uma instrução de extensão de sinal), afetando apenas padrões específicos de loop.
  • Outros pedem esclarecimentos; as explicações focam em como compiladores raciocinam sobre índices de loop e indexação de arrays sob a suposição de ausência de overflow.
  • Há apoio para ou definir overflow com sinal como wrap (como -fwrapv) ou garantir traps de overflow; o UB atual é visto como um mau compromisso por um ganho de velocidade minúsculo.

std::bit_cast, unions e avaliação em tempo de compilação

  • Alguns ficam aliviados por ter std::bit_cast para substituir type punning baseado em unions, que é UB em C++ mesmo que compiladores normalmente o “aceitem” na prática.
  • Isso ajuda revisores a defender padrões mais seguros, especialmente para estudantes vindos de C.
  • std::is_constant_evaluated() é mencionado, mas não realmente explorado; seus benefícios para workloads pesados de numérico/física permanecem pouco claros no thread.

Coroutines e tooling / depurabilidade

  • Coroutines estão na lista de “boas” para alguns: podem ser mais rápidas e mais seguras em termos de tipos do que callbacks e muitas vezes tornam o fluxo assíncrono mais fácil de ler.
  • Um grande ponto de dor é a depuração: stack traces para crashes em coroutines muitas vezes mostram apenas frames do framework/boilerplate, não o código do usuário.
  • Alguns observam que depurar código pesado em callbacks também é doloroso e ainda preferem coroutines apesar das lacunas nas ferramentas.

Ranges, lambdas e controle de fluxo “não linearizante”

  • Uma perspectiva é que o uso intenso de ranges e lambdas inline torna o código mais difícil de seguir:
    • Código fora das lambdas executa primeiro, enquanto os corpos das lambdas podem executar depois, várias vezes ou nunca.
    • Capturas por referência/valor podem levar a bugs sutis de tempo de vida e mutação se não forem cuidadosamente raciocinadas.
  • Outros reconhecem isso, mas ainda acham coroutines e abstrações de nível mais alto valiosas em comparação com callbacks profundamente aninhados.