C++ por Versão — O Que Mudou do C++98 ao C++26
C++ · Linguagem · Biblioteca Padrão
Seu projeto usa C++. Mas qual C++?
Entre gerenciar recursos na mão e compor operações com ranges, existe uma história de decisões de engenharia. Conhecer essa história ajuda a escrever código melhor hoje.
Você abre um projeto e encontra new, delete, ponteiros crus e um monte de classes que só existem para passar uma função para um algoritmo. Abre outro e vê unique_ptr, lambdas, concepts e ranges. Os dois são C++. A experiência de manter cada um pode ser bem diferente.
Saber em qual versão uma feature apareceu evita duas coisas: reinventar o que a biblioteca já entrega e mandar para produção código que a toolchain do time não consegue compilar.
Este é um mapa das principais mudanças de cada edição, com exemplos e consequências práticas. Não é uma lista de todos os defect reports. A data de referência é 15 de setembro de 2026. Há uma versão em inglês.
O Mapa Rápido
Seção intitulada “O Mapa Rápido”| Versão | Linguagem: destaques | Biblioteca: destaques |
|---|---|---|
| C++98 | Primeira padronização ISO de classes, templates, exceções e namespaces | STL, strings e streams |
| C++03 | Correções e value-initialization | Ajustes de especificação e interoperabilidade |
| C++11 | auto, lambdas, move semantics, constexpr, variadic templates | Smart pointers, threads, atomics, chrono, unordered containers |
| C++14 | Lambdas genéricas, init-capture, constexpr mais flexível | make_unique, shared_timed_mutex, integer sequences |
| C++17 | Structured bindings, if constexpr, fold expressions, CTAD | optional, variant, any, string_view, filesystem |
| C++20 | Concepts, modules, coroutines, <=>, consteval | Ranges, span, format, jthread, sincronização |
| C++23 | Deducing this, if consteval, subscrito multidimensional | expected, print, mdspan, generator, mais ranges |
| C++26 | Reflection, contracts, pack indexing, expansion statements | Senders/receivers, SIMD, inplace_vector |
Leia a tabela como uma seleção de marcos, não como uma promessa de suporte. Versão da linguagem, implementação do compilador e implementação da biblioteca são três coisas diferentes. As tabelas oficiais de GCC e Clang separam esses detalhes.
C++98 — A Base Que Continua no Seu Código
Seção intitulada “C++98 — A Base Que Continua no Seu Código”Classes e templates já existiam antes de 1998. O marco foi a primeira padronização internacional: uma base comum para a linguagem e sua biblioteca, incluindo containers, iteradores e algoritmos da STL. História por Stroustrup.
O exemplo abaixo já expressava uma ideia poderosa: o algoritmo não precisa conhecer o container inteiro. Recebe um intervalo e trabalha sobre ele.
// C++98#include <algorithm>#include <cassert>#include <vector>
int main() { int raw[] = {30, 10, 20}; std::vector<int> prices(raw, raw + 3); std::sort(prices.begin(), prices.end()); assert(prices.front() == 10);}O que levar para hoje: RAII — associar a liberação de um recurso ao tempo de vida de um objeto — já era uma técnica fundamental. C++ moderno melhorou as ferramentas para aplicá-la. Não inventou a ideia de que um destrutor deve fechar o arquivo ou liberar o recurso.
C++03 — Acertando as Regras
Seção intitulada “C++03 — Acertando as Regras”C++03 foi uma revisão de correções, sem a transformação de programação que viria em 2011. Um ajuste relevante foi a value-initialization, importante nas regras de inicialização de objetos. Stroustrup descreve essa edição como uma revisão pequena do padrão anterior. FAQ.
// C++03#include <cassert>
struct Counters { int accepted; int rejected; };
int main() { Counters counters = Counters(); assert(counters.accepted == 0 && counters.rejected == 0);}Não generalize para “todo objeto começa zerado”. Uma variável escalar local sem inicialização continua sendo um problema. Tampouco atribua unordered_map ao C++03: TR1 foi um relatório técnico separado; esse container entrou no padrão principal em C++11.
C++11 — A Virada do C++ Moderno
Seção intitulada “C++11 — A Virada do C++ Moderno”auto passou a deduzir tipos; lambdas aproximaram código e uso; referências rvalue e move semantics permitiram transferir recursos. Vieram também nullptr, range-for, enum class, override, final, noexcept, = default, = delete, inicialização por listas, static_assert e constexpr.
Na biblioteca, unique_ptr, shared_ptr, weak_ptr, thread, mutexes, atomics, futures, chrono, array, tuple e containers unordered mudaram o cotidiano. A FAQ de C++11 de Stroustrup explica a motivação dessas ferramentas.
// C++11#include <cassert>#include <memory>#include <utility>
int main() { std::unique_ptr<int> original(new int(42)); auto owner = std::move(original); assert(!original && *owner == 42);}Aqui, o recurso tem um dono explícito. std::move permite selecionar a operação de movimento; sozinho, ele não transfere nada. É o construtor de unique_ptr que realiza a transferência. Para outros tipos, mover pode ter custos diferentes — ou até selecionar uma cópia se não houver operação de movimento adequada.
Aplicação prática: comece uma modernização esclarecendo ownership. Trocar sintaxe antes de entender quem possui cada recurso deixa o problema central intacto.
C++14 — Menos Atrito no Dia a Dia
Seção intitulada “C++14 — Menos Atrito no Dia a Dia”As lambdas ganharam parâmetros auto e capturas com inicialização. Funções puderam deduzir o retorno; variable templates e separadores de dígitos chegaram. constexpr passou a aceitar mais construções, incluindo loops. Na biblioteca, vieram make_unique e ferramentas como integer_sequence. Histórico da linguagem no Clang e biblioteca no GCC.
// C++14#include <cassert>#include <memory>
constexpr int sum_to(int n) { int total = 0; for (int i = 1; i <= n; ++i) total += i; return total;}
int main() { auto add = [](auto a, auto b) { return a + b; }; auto answer = std::make_unique<int>(add(20, 22)); static_assert(sum_to(4) == 10, "unexpected sum"); assert(*answer == 42);}Repare na cronologia: unique_ptr é C++11, make_unique é C++14. Uma feature pequena pode eliminar uma construção manual repetida em centenas de lugares.
C++17 — Tipos Mais Expressivos, Templates Mais Legíveis
Seção intitulada “C++17 — Tipos Mais Expressivos, Templates Mais Legíveis”Structured bindings dão nomes às partes de um objeto. if constexpr escolhe ramos em tempo de compilação. Fold expressions simplificam operações sobre parameter packs; CTAD deduz argumentos de templates de classes. Inline variables facilitam definições em headers.
optional, variant e any modelam situações diferentes; string_view representa uma visão não proprietária de texto; filesystem padroniza operações com caminhos. Também chegaram políticas de execução para algoritmos. Status da biblioteca C++17.
// C++17#include <cassert>#include <map>#include <optional>#include <string>
std::optional<int> lookup(const std::map<std::string, int>& stock, const std::string& symbol) { if (auto it = stock.find(symbol); it != stock.end()) { const auto& [name, quantity] = *it; return quantity; } return std::nullopt;}
int main() { const std::map<std::string, int> stock{{"ABC", 7}}; assert(lookup(stock, "ABC").value_or(0) == 7); assert(!lookup(stock, "XYZ"));}Ausência agora aparece no tipo. Você não precisa escolher um número mágico e esperar que todo consumidor se lembre dele.
Dois cuidados: string_view não prolonga a vida da string original; políticas paralelas não prometem ganho automático. Medir ainda faz parte do trabalho. E a elisão garantida de cópia de C++17 vale para casos específicos com prvalues, não para toda forma de retorno, como NRVO.
C++20 — Uma Mudança de Escala
Seção intitulada “C++20 — Uma Mudança de Escala”Quatro nomes organizam a conversa: concepts, ranges, coroutines e modules. Concepts expressam requisitos de templates. Ranges aproximam algoritmos e intervalos. Coroutines permitem suspender e retomar uma execução. Modules oferecem uma alternativa à organização baseada apenas em inclusão textual.
Também chegaram <=>, consteval, constinit, designated initializers e mais capacidade de avaliação constante. A biblioteca ganhou span, format, jthread, stop tokens, semáforos, latches, barriers e operações de espera em atomics. Biblioteca C++20.
// C++20#include <cassert>#include <concepts>#include <ranges>#include <vector>
template<std::integral T>constexpr T twice(T value) { return value + value; }
int main() { std::vector<int> values{1, 2, 3, 4}; auto selected = values | std::views::filter([](int n) { return n % 2 == 0; }) | std::views::transform([](int n) { return twice(n); }); int total = 0; for (int n : selected) total += n; assert(total == 12);}Esse pipeline é uma view: percorremos os valores sem materializar outro vector. Os dados de origem precisam continuar vivos.
As pegadinhas: coroutine não cria thread nem entrega um runtime de I/O. constinit não torna uma variável imutável. Modules exigem integração da toolchain e do build; substituir #include por import mecanicamente não é um plano de migração.
C++23 — Resultados, Impressão e Composição
Seção intitulada “C++23 — Resultados, Impressão e Composição”Na linguagem, os destaques incluem parâmetro explícito de objeto (deducing this), if consteval e operator[] com múltiplos argumentos. Na biblioteca, mdspan representa dados multidimensionais sem ser dono deles; generator oferece um gerador baseado em coroutine; ranges ganharam adaptadores e ranges::to. Status C++23 da libc++.
expected torna explícito “valor ou erro”. Já print e println levam a formatação à saída diretamente. Veja as propostas de expected e impressão formatada.
// C++23 — exige uma biblioteca com std::expected#include <cassert>#include <expected>
enum class Error { invalid_quantity };
std::expected<int, Error> validate(int quantity) { if (quantity <= 0) return std::unexpected(Error::invalid_quantity); return quantity;}
int main() { const auto result = validate(10); assert(result && *result == 10); const auto failure = validate(0); assert(!failure && failure.error() == Error::invalid_quantity);}optional responde “tem valor?”. expected acrescenta “se falhou, por quê?”. Isso ajuda em validação, parsing e APIs nas quais falha é um resultado esperado. A escolha entre retorno explícito e exceções continua dependendo do contrato da aplicação.
Outra distinção útil: format chegou em C++20; print chegou em C++23. C++23 foi finalizado em 2023 e publicado como ISO/IEC 14882:2024; o apelido da versão não é necessariamente o ano da publicação ISO.
C++26 — Reflection, Contracts e Execução Assíncrona
Seção intitulada “C++26 — Reflection, Contracts e Execução Assíncrona”O WG21 concluiu o trabalho de C++26 em março de 2026. Isso não equivale a suporte completo em toda toolchain. O relato de um participante da reunião de Croydon registra esse marco; confira a implementação recurso por recurso antes de adotar.
- Reflection estática: consultar informações do programa em tempo de compilação, útil para reduzir repetição em serialização e adaptação de tipos. P2996.
- Contracts: expressar pré-condições, pós-condições e asserções de contrato. Não substituem validação de entrada não confiável.
- Pack indexing e expansion statements: mais ferramentas para selecionar e expandir elementos em código genérico.
- Senders/receivers: modelo de composição de operações assíncronas em
std::execution. É diferente das políticas de execução introduzidas em C++17. P2300. - SIMD e
inplace_vector: abstrações para paralelismo de dados e um container de tamanho variável com capacidade fixa e armazenamento interno. Status da biblioteca.
Meu critério de adoção: primeiro um experimento isolado, depois cobertura de compiladores e plataformas, por último uma interface pública. Uma feature nova na fronteira de uma biblioteca pode obrigar todos os consumidores a atualizar juntos.
Qual Versão Usar no Seu Projeto?
Seção intitulada “Qual Versão Usar no Seu Projeto?”Comece pela versão mais recente que a matriz real de suporte do projeto sustenta. Um serviço com ambiente controlado e um SDK distribuído para terceiros têm restrições diferentes.
- Fixe o padrão no build. Não dependa do default do compilador.
- Confira linguagem e biblioteca. Trocar o executável do compilador pode manter uma biblioteca padrão antiga.
- Teste a feature de que você precisa. Feature-test macros ajudam;
__cplusplussozinho não prova suporte completo. - Valide ABI e dependências. Compilar um arquivo de exemplo não garante compatibilidade entre bibliotecas binárias.
- Migre em etapas. Ownership, tipos de retorno e algoritmos são pontos de partida concretos. Meça desempenho com a carga real.
Para executar um exemplo, salve o bloco em example.cpp e selecione a edição correspondente. Em uma toolchain GCC ou Clang compatível:
clang++ -std=c++20 -Wall -Wextra -pedantic example.cpp -o exampleNo CMake, para um alvo existente:
target_compile_features(app PRIVATE cxx_std_20)set_target_properties(app PROPERTIES CXX_EXTENSIONS OFF)Isso pede pelo menos C++20; não certifica que todo recurso da edição está implementado. Para detalhes de disponibilidade, consulte GCC, Clang e a biblioteca utilizada.
Continue a Leitura
Seção intitulada “Continue a Leitura”- C++ em HFT: onde a latência aparece de verdade.
- Cache affinity: o custo de movimentar dados.
- Referência técnica para aprofundar os fundamentos.
Conhecer as versões ajuda a fazer uma pergunta melhor no code review: essa construção resolve o problema de forma mais clara, com os custos e o suporte que precisamos? O número depois de -std= é só o começo.