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.hcriticado; 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 superariostreamse é 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
dylibexiste, 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.
- Alguns afirmam que Rust “não permite” bibliotecas dinâmicas em geral; outros apontam que
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_castpara 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.