A biblioteca padrão de C++ vem se desfazendo há 15 anos, e as provas são públicas
(hftuniversity.com)- A biblioteca padrão de C++ tem repetidamente depreciado formalmente designs ruins ou os deixado de lado ao lado de novos substitutos desde o C++11, criando uma estrutura em que o desenvolvedor precisa saber em que fase está cada “camada que não deve ser usada”
- A camada de retirada oficial inclui itens com papers de depreciação e remoção, como
std::auto_ptr, especificações dinâmicas de exceção, a interface de coleta de lixo do C++11 estd::aligned_storage;std::functiontambém entra num fluxo de substituição de 15 anos cercado porstd::move_only_function,std::copyable_functionestd::function_ref - A camada de evasão informal inclui recursos que continuam no padrão, mas são evitados em código de produção, como o lento
std::regex,std::async, cujo destrutor espera e cria armadilhas de deadlock, além de<iostream>,std::list,std::dequeestd::vector<bool> - O problema dos contêineres padrão aparece com força em
std::unordered_map,std::mapestd::list; em um benchmark com a mesma carga de trabalho, o P99 da implementação ingênua em C++ foi de 302.653 cycles, contra 5.177 cycles da implementação ingênua em Rust, uma diferença de 58x - A escolha pela estabilidade de ABI é a diferença central: enquanto outras linguagens reduzem erros com remoções, edições ou transições de versão principal, o C++ preserva na prática defaults ruins dentro de
std::de forma quase permanente
Ponto de partida: a classificação de std::function como “legado”
- A tabela de referência rápida de Sandor Dargo para
std::copyable_functionclassificastd::functioncomo “Legacy. Avoid in new code.” std::functionentrou no C++11, e o wrapper substituto mais recente,std::copyable_function, entra no C++26; o argumento de venda do novo recurso está mais próximo de “não use o original” do que de “use isto quando precisar de um callable copiável”- O
const operator()destd::functiontem um defeito de const-correctness que permite chamar callables non-const, algo que não pode ser corrigido sem quebrar a ABI - Em resposta a esse defeito,
std::move_only_functionentrou no fluxo do P0288R9 no C++23,std::copyable_functionno P2548R6 no C++26, estd::function_refno P0792R14 no C++26
Recursos do padrão oficialmente revertidos
std::auto_ptrteve sua semântica de cópia-movimento quebrando código genérico e contêineres padrão, foi marcado para depreciação no C++11 e removido no C++17 via N4190; o mesmo paper também removeu os adaptadores de<functional>do C++98 estd::random_shufflestd::random_shufflefoi substituído porstd::shuffleporque dependia destd::rande de estado global- As especificações dinâmicas de exceção
throw(X, Y)foram marcadas para depreciação no C++11 e removidas no C++17 por P0003R5; o aliasthrow()foi removido no C++20 por P1152 std::iteratorfoi marcado para depreciação no C++17 por P0174R2, e sua remoção no C++26 é promovida por P3365R1; a alternativa é definir diretamente os cinco typedefsstd::aligned_storageestd::aligned_unionentraram no C++11 e foram marcados para depreciação no C++23 por P1413R3; os problemas citados incluem boilerplate comtypename ::type,reinterpret_cast, comportamento indefinido comLen == 0e ausência de constexprstd::not1,std::not2,unary_negateebinary_negateforam marcados para depreciação no C++17, removidos no C++20 e substituídos porstd::not_fnde P0005- A família
std::declare_reachable, interface de coleta de lixo do C++11, foi removida no C++23 por P2186R2, já que implementações principais nunca ofereceram um coletor de lixo real - Concepts TS, Modules TS, Coroutines TS, Reflection TS, Executors TS e Networking TS também passaram por redesenho, substituição ou atraso antes da fusão; Reflection mudou para P2996, e Executors migrou para o fluxo sender/receiver de P2300
Recursos que continuam no padrão, mas são evitados na prática
std::regexentrou no C++11, mas o P1844R1 registrou em comitê que seu desempenho é “muito ruim em comparação com outras soluções disponíveis”; o fluxo substituto aponta para CTRE e P1433R0, e fora do padrão para Boost.Regex, RE2 e PCRE2std::asyncbloqueia no destrutor do future retornado até a conclusão da tarefa assíncrona, e N3679 registra a armadilha de deadlock causada por isso<iostream>é lento, preso a locale, não é thread-safe em formatação e tem mensagens de erro notoriamente ruins, mas ainda assim não foi marcado para depreciação mesmo após a chegada destd::formatno C++20 via P0645 e destd::print/std::printlnno C++23 via P2093std::listfoi mostrado por Bjarne Stroustrup no keynote da GoingNative 2012 como perdendo até em cargas de trabalho de inserção no meio parastd::vector, e o texto posterior Are lists evil? responde algo bem próximo de “sim”std::dequeaparece na issue pública microsoft/STL#147, onde se registra que o tamanho de bloco imposto pelo padrão é pequeno demais e que será necessária uma grande reforma de desempenho no próximo ABI breakstd::valarrayentrou em 1998 como contêiner numérico, mas a otimização por expression templates não se concretizou e, segundo o cppreference, as implementações não parecem ter código especial além do que existe para contêineres comunsstd::vector<bool>tem como análise de referência Onvector<bool>, de Howard Hinnant; o armazenamento compactado em bits é útil, mas o fato de ser nomeado como uma especialização destd::vectoro torna uma armadilha que causa comportamento incorreto em código genérico quandoT = boolvolatilefoi marcado para depreciação no C++20 por P1152R4 em operações compostas e em posições de parâmetro e retorno, depois parcialmente revertido no C++23 por P2327R1, com nova reversão adicional prevista no C++26 por P2866R0
Contêineres básicos que a ABI impede de consertar
std::unordered_mapé praticamente impedido de usar open addressing pelas exigências de buckets e estabilidade de iteradores da especificação do C++11; a estrutura SwissTable do Google é apresentada como tendo cerca de 3x de vantagem de desempenho sobrestd::unordered_map- Folly F14, Boost
unordered_flat_mapeankerl::unordered_densesão substitutos na mesma linha; oHashMapde Rust usa como default da biblioteca padrão um port da SwissTable do hashbrown std::mapestd::setsão red-black trees baseadas em nós, exigindo alocação no heap por nó e pointer chasing a cada iteração; Abseilbtree_mape RustBTreeMapevitam esse problema com estruturas baseadas em B-tree- O C++23 adicionou
std::flat_mapestd::flat_setcom P0429R9, mas não consegue mudar o design básico destd::unordered_map,std::mapestd::list - O benchmark de livro de ordens multi-símbolo compara, na mesma carga, com a mesma seed e no mesmo core isolado,
std::unordered_map+std::map+std::listem C++ comHashMap+BTreeMap+VecDequeem Rust
| Implementação | P99 cycles |
|---|---|
C++ naive (unordered_map + map + list) |
302,653 |
C++ step 1 (flat_hash_map + map + deque) |
9,951 |
C++ step 2 (flat_hash_map + btree_map + deque) |
9,114 |
C++ step 3 (flat_hash_map + btree_map + vector) |
4,268 |
Rust naive (HashMap + BTreeMap + VecDeque) |
5,177 |
- A troca de
std::listporstd::vectorsozinha mostra cerca de 70x; a troca destd::unordered_mapporflat_hash_map, entre 3x e 5x; e a troca destd::mapporbtree_map, 1,09x, um efeito dentro da faixa de ruído - O ponto da comparação não é que a linguagem Rust seja 58x mais rápida que C++, mas que a biblioteca padrão de Rust escolheu defaults corretos, enquanto a biblioteca padrão de C++ não consegue corrigir seus três defaults por causa da ABI
O problema do Vasa e o acúmulo de recursos
- No documento WG21 P0977R0 de 2018, “Remember the Vasa!”, Bjarne Stroustrup usa o afundamento do navio de guerra sueco Vasa em 1628 como metáfora e diagnostica que o comitê tem “cerca de 150 cozinheiros” e não trata suficientemente o impacto de cada recurso no sistema como um todo
std::simdé tratado como exemplo representativo do mesmo padrão em std::simd Is a Solution to the Wrong Problem; o recurso começou na biblioteca Vc de Matthias Kretz, passou por P0214, Parallelism TS 2 e P1928, e entrou no C++26- Quando
std::simdentrou no padrão, já existiam fora dele Google Highway, ISPC, EVE, xsimd e SIMDe, e argumenta-se que até o auto-vectorizer de GCC e Clang melhorou a ponto de loops escalares com-O3 -march=nativesuperaremstd::simd std::simdcompila 10x mais devagar que código escalar equivalente, fica atrás do auto-vectorizer que pretendia substituir e não consegue expressar vetores de largura escalável do ARM SVE nem runtime dispatch- As três implementações, libstdc++, libc++ e MSVC STL, são mantidas por equipes de engenharia de um dígito, e cada novo recurso padrão amplia a matriz de testes, os bugs de conformidade, as interações entre recursos e os itens do bug tracker herdados pelo próximo mantenedor
std::regexcarrega problemas conhecidos há 15 anos,std::dequetem uma issue pedindo redesenho, e os modules do C++20 são descritos como ainda não funcionando de forma limpa nas três implementações mesmo seis anos após a padronização- O conhecimento prático para operar C++ moderno acaba concentrado em uma pequena minoria de especialistas dedicados, que aprenderam a cronologia das camadas erradas, os workarounds de terceiros, as diferenças entre as três implementações da biblioteca padrão e a distância entre teoria e prática
Diferença em relação a outras linguagens: não é errar, é a taxa de retenção
- Python removeu mais de 20 módulos da biblioteca padrão com a PEP 594, removeu
distutilsno Python 3.12 com a PEP 632 e a PEP 387 explicita autoridade para encurtar o ciclo de depreciação de recursos perigosos ou quebrados - Java marcou a API de Applet para depreciação no Java 9, para remoção no Java 17, e completou o caminho de 8 anos até a remoção real no JEP 504; Nashorn foi removido no Java 15 pelo JEP 372
- O Java SecurityManager foi colocado em estado de depreciação para remoção pelo JEP 411, foi permanentemente desativado pelo JEP 486, e o JEP 398 trata do caminho de remoção da API de Applet
- Rust escolhe por crate as edições 2015, 2018, 2021 e 2024 em
Cargo.toml;mem::uninitializedfoi substituído porMaybeUninit,std::error::Error::descriptionporsource, e a macrotry!pelo operador? - C# aceitou transições de versão principal ao passar de .NET Framework para .NET Core, deixando para trás BinaryFormatter, AppDomains, Remoting, Code Access Security, WCF server e WebForms
- JavaScript quase não remove recursos por restrições de compatibilidade com a web, mas cancelable promises foi retirado ainda no Stage 1, SIMD.js foi abandonado em favor de WebAssembly SIMD, e Go, por causa da promessa de compatibilidade do Go 1, optou por deixar
io/ioutilapenas como deprecado - A diferença do C++ não está em ter cometido erros, mas na taxa de retenção com que quase nunca consegue remover itens como
std::regex,std::unordered_map,std::vector<bool>,std::valarraye o defeito de const-correctness destd::function
A estabilidade de ABI cria preservação permanente
- P1863R1 “ABI - Now or Never” foi o fluxo que perguntou se o C++23 deveria aceitar um ABI break ou escolher estabilidade de ABI permanente, e o comitê na prática escolheu estabilidade permanente
- Essa escolha dificulta corrigir
std::regex, migrarstd::unordered_mappara open addressing e mudar a estrutura destd::list,std::mapestd::deque - A ABI da biblioteca padrão de C++ é imposta pelo linker dinâmico: objetos compilados com uma versão de libstdc++ precisam se ligar a objetos de outra versão, então detalhes como o layout de
std::stringe a composição destd::regex_traitsficam congelados nos binários distribuídos - Essa restrição é detalhada em documentos como a política de ABI do libstdc++ e o Itanium C++ ABI
- Usuários de Python escolhem
python==3.12, usuários de Rust escolhem a edição emCargo.toml, usuários de Java escolhem a versão do JDK e usuários de C# escolhem um TFM comonet6.0ounet8.0, mas o C++ não tem umCargo.tomlparastd:: -std=c++26escolhe quais headers e regras de linguagem usar, mas não oferece outrostd::stringnem umstd::unordered_mapredesenhado- Por isso, em 2026, a biblioteca padrão de C++ usada em produção ainda carrega, por design e por imposição, defaults ruins que o comitê aceitou desde 1998
- Codebases modernos de C++ em trading firms tier-one, mecanismos de busca e navegadores dependem fortemente de bibliotecas não padronizadas como Boost, Abseil, Folly, EASTL,
//basedo Chromium, contêineres próprios, allocators customizados, CTRE, Outcome e bibliotecas de corrotinas
2 comentários
O texto original é bem extenso, e lendo até o fim dá bastante a sensação de devoção ao Rust.
Ainda assim, acabei aprendendo muitas coisas que não sabia. Obrigado pelo bom texto.
Opiniões no Lobste.rs
Pensando se houve um churn parecido no ecossistema Rust, parecem ter sido só alguns casos grandes
Na época da Leakpocalypse, chegou-se à conclusão de que não dava para depender que destrutores
Dropsempre seriam executados para preservar invariantes de segurança, e quase não houve mudanças reais de API, exceto pela remoção destd::thread::scoped. Depois surgiu um substituto que faz a mesma coisa de forma soundstd::mem::uninitializedfoi descontinuado e hoje é considerado unsound. Os tiposRangeexistentes devem ser substituídos lentamente por tipos novos quase idênticos para corrigir problemas relativamente pequenos de API.std::error::Error::descriptionfoi descontinuado porque a maioria dos tipos de erro não quer armazenar strings, e existe um substituto direto na forma da implementação deDisplayConsiderando que
stdficou estável por 11 anos, isso é bem impressionante, e o restante destdainda existe e funciona, e 98% ainda é considerado Rust idiomático. Já a biblioteca padrão de C++ parece estar numa posição perigosa, leve demais no gatilho para adicionar funcionalidades e surpreendentemente conservadora para descontinuar qualquer coisaIteratorpegar emprestado o próprio conteúdo. É um problema crônico que continua aparecendo nas conversas sobre Rust como “por que não posso usar isso e preciso de um workaround?”Do mesmo modo, o fato de
f32ef64não implementaremCmpe terem em vez disso o métodof32::total_cmptambém é um incômodo frequente para quem está começando, então a gente acaba suspirando e explicando o contextoO aparato de formatação de panic também não é muito bom, e há muitos posts de blog dizendo que o panic handler padrão usa formatação, é difícil de desativar e acaba consumindo bastante do tamanho do executável
Pessoalmente, acho que o design envelhecido da biblioteca padrão prejudica bastante a popularidade e a usabilidade de C++
Muitos problemas atribuídos à linguagem em si na verdade deveriam ser direcionados à biblioteca padrão
Por exemplo, dizer que “C++ compila devagar” não é exatamente correto. Não é inerentemente lento por usar recursos de C++; o que deixa lento é a enorme hipertrofia de headers e dependências, além de a biblioteca padrão usar templates em excesso até para abstrações simples
“C++ não é seguro” também é parcialmente verdade, mas o design da biblioteca padrão piora isso. Não há motivo para não aplicar a uma nova biblioteca padrão padrões mais seguros de design de API usados em Rust. Claro, uma das grandes vantagens de C++ é a compatibilidade retroativa, então é um problema muito complexo
vec[idx]lançar exceção ou abortar em vez de fazer acesso fora dos limites/comportamento indefinido. Mas, em muitos casos, por causa das diferenças da linguagem, é muito mais difícil criar APIs seguras em C++Rust tem movimentação destrutiva por padrão; C++ não. Por isso, APIs de smart pointers inevitavelmente acabam sendo unsafe ou, no mínimo, surpreendentes e propensas a crash. Por exemplo, abortando o programa ao acessar um smart pointer após ele ter sido movido
Rust tem anotações de tempo de vida; C++ não. Então Rust consegue evitar coisas como invalidação de iteradores no design da API de iteradores, enquanto em C++ isso é praticamente difícil de verdade. Rust também tem pattern matching, o que permite que APIs como
Optionofereçam de forma ergonômica a abordagem de “verificar e usar logo em seguida”. C++ até poderia fornecer uma versão destd::optionem que acessar um valor vazio não gerasse UB, mas ela seria muito mais incômoda de usar do que no C++ atual ou no Rust. O operador?do Rust também ajuda muito aquiSei que dá para acrescentar algo parecido com pattern matching em C++ com um conjunto de overloads, como em
std::variant, mas me parece bem mais difícil de usar e mais fácil de errarSó uma biblioteca moderna com strings e arrays, alguns containers genéricos e suporte nativo a allocator já tornaria C muito mais ergonômico e fácil de usar. Claro, alguns defeitos da linguagem não desaparecem só trocando a biblioteca, mas ainda assim isso já levaria bem longe
Se você olhar codebases modernas em C, elas usam extensivamente bibliotecas customizadas para allocator, strings, vector, hash table e operações de sistema de arquivos, e se você tiver experiência com C ou com gerenciamento manual de recursos, não é tão difícil seguir nessa linha
slice<T, N>capaz de representar “um ponteiro para exatamente N bytes” ou “um ponteiro para uma quantidade arbitrária de bytes”Ela tem
head(n),tail(n),slice(start, end)e operador de índice, e todos fazem verificação de limitesÉ realmente um prazer trabalhar com abstrações assim, mas, para obter uma linguagem moderna e razoavelmente segura, na prática você acaba tendo que portar as bibliotecas padrão de Rust e Zig para C++. Ainda assim, no fim vale o esforço investido
Se vai escrever um texto desses, por favor escreva você mesmo. Mesmo que a lista tenha sido feita manualmente, jogar isso num LLM e despejar o resultado numa página para pessoas lerem é rude demais. Se eu vir mais uma vez uma frase dizendo que um “engenheiro em atividade” aprende desde o “primeiro dia” a evitar a “funcionalidade X”, vou enlouquecer
O vergonhoso é que aqui existe muito a ser dito, mas no fim não se diz nada. Deve ter havido um motivo para produzir esse texto, então eu queria que esse motivo fosse dito. Alguma parte de C++ deve ter te irritado, e alguma funcionalidade deve ter te confundido. O motivo de essas funcionalidades serem ruins não é só um fracasso objetivo de design, mas também o efeito que elas têm sobre nós
Se você já usou
std::iteratore levou bronca no Slack, ou deixou de usarreinterpret_castporque ele tem 16 letras e ia estragar um pouco a formatação da linha, seria melhor ver esse tipo de história no Lobsters. Se essas histórias não existem, então não invente à força e não faça uma GPU gerar a mesma frase 10 vezes com multiplicação de matrizes. Comente só as partes que merecem comentário e escreva o resto em tabelas e bullet pointsUso C++ há 20 anos e ainda uso, mas concordo bastante com este texto. O realmente bom em usar Rust hoje em dia, mais do que a segurança de memória, é a excelente biblioteca padrão e o ecossistema de pacotes
Um exemplo representativo é a biblioteca de ranges. Já faz 6 anos que foi padronizada, mas as principais bibliotecas padrão ainda não conseguiram implementá-la completamente e, mesmo quando implementam, há só alguns poucos combinadores. O equivalente em Rust, os métodos de
Iterator, são 76, e com um únicocargo addsurgem mais 130 via traititertoolsOutra coisa de que sinto muita falta é pattern matching. Dá para tornar tipos union como
std::variantergonômicos. A proposta está em discussão, mas ainda não entrou nem no C++26, e isso é uma pena. Em compensação, contracts e executors estão entrando, mas sinceramente nunca vi ninguém ao meu redor pedindo issoEm geral, o critério que eu vejo é o seguinte. Se uma funcionalidade dá suporte a casos de uso desejáveis e não pode ser expressa na biblioteca padrão, então ela deve entrar na linguagem. Sempre que possível, o ideal é decompor a funcionalidade desejada em elementos mínimos e independentes que também possam ser usados para outros fins
Funcionalidades usadas em praticamente toda base de código devem entrar na biblioteca padrão. Se um tipo é comumente usado como interface entre bibliotecas, ele deve entrar na biblioteca padrão. Ninguém quer que toda biblioteca defina seu próprio tipo de tuple ou sua própria string. Em C++, com o primeiro caso isso de fato acontecia até o C++11, e com o segundo ainda acontece porque
std::stringé um desastre. Isso também se aplica a tipos de interface, e hoje em dia o C++ lida com isso principalmente com conceptsO resto deve ir para bibliotecas reutilizáveis e modulares. Rust é bastante bom em ter um conjunto estável e blessed de bibliotecas externas, então a pressão do tipo “toda engine escrita em Rust precisa desta estrutura de dados, então vamos colocá-la na biblioteca padrão” é muito menor. Quem faz jogos pode simplesmente importar os crates de que precisa. C++ nunca assimilou de verdade a ideia de “um bom pacote para recomendar a um problema que muita gente tem, mas não a maioria”
O que me preocupa é quais das coisas que estão sendo adicionadas agora acabarão sendo revertidas no futuro. Contracts acabou de entrar no C++26, e já estão apontando falhas graves de design
Não quero criticar genericamente o “design por comitê”. Acho que organizações assim cumprem um propósito importante e têm pontos fortes próprios. Só que essa força não está em projetar funcionalidades totalmente novas do zero, em campo aberto
Onde WG21 e WG14 realmente brilham é em pegar funcionalidades cujo espaço de design já foi explorado até certo ponto e, se possível, já com várias implementações existentes, e transformá-las em recursos padronizados que a maioria dos usuários e implementadores consiga aceitar.
std::embedé um exemplo dissoEm contrapartida, quando se padroniza cedo demais — como com a extensão de GC mencionada no artigo,
std::memory_order_consumeou os modules do C++20 — antes mesmo de alguém conseguir implementar direito, a coisa tende a dar muito erradoFiquei bem chocado quando percebi, há um tempo, que o C++ não versiona a biblioteca padrão. Não esperava que este texto fosse apontar isso tão diretamente
Também achei interessante a menção de que o Go é de forma parecida conservador em relação à compatibilidade futura. Mas o Go também é igualmente conservador até nas funcionalidades que adiciona, então parece ter evitado a maior parte dos problemas do C++. O fato de não ter uma ABI estável provavelmente também ajudou
Entre as bibliotecas populares que conheço, a única que expõe explicitamente uma ABI de C++ é a libcamera, e isso é bem incômodo. Pela minha experiência, até bibliotecas C++ normalmente exportam símbolos com ABI C, e isso também facilita a interoperabilidade com outras linguagens. Posso estar deixando passar alguma coisa
E entre Clang e MSVC não existem umas quirks de compatibilidade de ABI? Lembro do Conan desencorajar explicitamente ou até proibir misturar compiladores, então fico em dúvida sobre por que o comitê de C++ se esforça tanto para preservar estabilidade de ABI
Há duas coisas intimamente relacionadas aqui: a especificação da biblioteca padrão e a implementação. A especificação é para a combinação completa linguagem+biblioteca, e a implementação em geral tenta suportar pelo menos uma ou mais versões da especificação
Existem muitas bibliotecas que expõem interfaces em C++, inclusive algumas bem grandes, como o Qt
O problema é que a máquina abstrata do C++ não define o processo de linkedição. Então ela não consegue definir como bibliotecas dinâmicas funcionam. O linking dinâmico de C++ em sistemas UNIX segue o modelo de C. Finge que é linking dinâmico e joga a responsabilidade para problemas do loader. Isso leva a coisas horríveis como copy relocation. O Windows tem uma noção muito mais fundamentada do que é uma biblioteca compartilhada, mas por causa disso alguns idioms de bibliotecas C++ de UNIX não funcionam no Windows
Bibliotecas compartilhadas são um grande problema para recursos como templates em C++. Se você quer poder instanciar um template com tipos do usuário, a definição completa precisa estar no header, porque o compilador não consegue enxergar além dos limites da unidade de compilação. Em bibliotecas compartilhadas, o mesmo código é instanciado em vários lugares. Se o programa e a biblioteca instanciam o mesmo template com os mesmos parâmetros, ambos ficam com cópias, e o linker e o loader precisam fazer com que só uma seja usada no programa final carregado
Comparando com Swift, o Swift declara explicitamente: “bibliotecas compartilhadas existem, e a linguagem expõe construções em nível de linguagem para representá-las”. Se você quiser expor genéricos além do limite de uma biblioteca compartilhada, isso é possível, mas para todos os chamadores externos eles são rebaixados para uma versão com despacho dinâmico. Em C++ também dá para implementar isso manualmente. Você faz um template de versão geral usando um wrapper com apagamento de tipo e escreve explicitamente outras instanciações concretas. Mas isso é difícil e manual. Em Swift, simplesmente “é assim que funciona no limite de uma biblioteca compartilhada”
O mesmo vale para ocultação de tipos. O C++ usa o padrão
pImplpara criar uma interface pública que expõe o comportamento além do limite da biblioteca, mas esconde a implementação. O Swift tem uma máquina abstrata que sabe onde estão os limites da biblioteca e diz: “o tamanho de um tipo que não foi explicitamente marcado como ABI-stable não é uma constante de tempo de compilação além do limite de uma biblioteca compartilhada”Isso também é outra forma de o padrão negar a realidade. Quase toda base de código C++ não trivial em que trabalhei foi compilada com
-fno-rtti -fno-exceptionsou com as opções equivalentes do CL.EXE. O padrão não reconhece isso como possibilidade. A maioria das funções da biblioteca padrão ainda pressupõe exceções para relatar erros, então, se você compilar com-fno-exception, ela simplesmente chamaabort. Isso faz com que elementos da biblioteca padrão que fazem alocação dinâmica de memória fiquem inutilizáveis em ambientes embarcados.std::vector<T>::push_backpode fazer o programa travarA parte do artigo que diz que “o comitê não só não consegue remover recursos ruins, como continua adicionando novos recursos que engenheiros de software da prática não pediram” é 100% igual ao modo como contracts surgiu. O Verus mostra o que um bom sistema de contracts pode possibilitar em uma linguagem voltada para ambientes como os do C++. O P2900 contracts é uma combinação de requisitos conflitantes, então piora qualquer problema para o qual contracts poderia servir
Não acho verdadeira a conclusão de que um “engenheiro de C++” recebe um salário muito maior do que um “engenheiro que sabe programar”. Na prática, ninguém escreve código exatamente segundo o padrão C++; cada um escreve de acordo com o seu subconjunto-de-um-superconjunto interno favorito
go vettambém tem valor aqui. Porque fornece upgrade automático para melhoria de APIDesde o ano passado, abandonei quase totalmente o C++ e fui primeiro para Kotlin, depois para Swift. Na empresa ainda preciso manter C++, mas o código novo que escrevo é muito mais limpo, conciso e seguro. Estou aceitando tradeoffs em tamanho do código e talvez em desempenho, mas vale a pena
Lembrei que a semântica do for loop do Go mudou quebrando compatibilidade retroativa, então achei que esta frase estava errada: https://go.dev/blog/loopvar-preview
Mas aí descobri que o Go também usa aqui uma abordagem parecida com as editions do Rust. Você precisa declarar a versão Go 1.22 ou superior para que a semântica mude. Talvez também desse para remover
io/ioutildesse jeito, mas aparentemente não vale a pena quebrar código até atravessando o limite de uma editionSe o C++ não tivesse realmente tentado essas más ideias e provado que eram más ideias, talvez o Rust não existisse na forma atual. Um grande obrigado!
Tenho interesse em um substituto da biblioteca padrão ao estilo Rust para C++. Conheço o rpp, que tem esse objetivo: https://github.com/TheNumbat/rpp
Haveria outras opções? Não estou falando de outras implementações da stdlib de C++, como EASTL, mas de bibliotecas que sigam o Rust mais de perto. Sei que algumas partes, como
std::initializer_list, estão embutidas na sintaxe, mas o resto todo pode ser trocado