C++ ocupa um lugar singular na história das linguagens de programação. Desde o final da década de 1970, persegue um objetivo difícil: permitir abstrações poderosas sem abdicar do controlo direto dos recursos nem de um desempenho próximo da máquina.
Esta tensão entre abstração e controlo explica grande parte da sua evolução. C++ incorporou classes, programação genérica, uma extensa biblioteca padrão, técnicas funcionais e concorrência, mantendo ao mesmo tempo uma forte continuidade com C e com décadas de software existente.
O resultado é uma linguagem simultaneamente poderosa e complexa. A sua história mostra que essa complexidade não resulta apenas de uma acumulação acidental: muitas vezes reflete compromissos deliberados entre compatibilidade, desempenho e capacidade para servir tipos de software muito diferentes.
De C a C with Classes
No início da década de 1970, C afirmou-se nos Bell Labs como uma ferramenta particularmente eficaz para desenvolver Unix. A linguagem de Dennis Ritchie combinava portabilidade relativa, acesso direto à memória e um modelo de execução simples, tornando-se rapidamente uma referência da programação de sistemas.
À medida que o software crescia, surgia outra questão: como estruturar sistemas de grande dimensão sem perder as qualidades que tornavam C tão útil?
No final dos anos 1970, Bjarne Stroustrup utilizou Simula durante o seu trabalho em Cambridge. Valorizava as suas classes e mecanismos de abstração, mas o desempenho disponível no seu ambiente não respondia a todas as necessidades.
Quando entrou nos Bell Labs em 1979, começou a procurar uma combinação: preservar a eficiência e o ecossistema de C e acrescentar abstrações inspiradas, entre outras fontes, em Simula. O projeto recebeu o nome C with Classes.
As primeiras versões ofereciam classes, construtores e destruidores, controlo de acesso e herança. Stroustrup desenvolveu também Cfront, que traduzia a nova linguagem para C antes de um compilador C existente produzir o programa final. Esta estratégia pragmática facilitou a experimentação e a utilização em sistemas que já dispunham de ferramentas C.
1983: nasce o nome C++
Em 1983, C with Classes passou a chamar-se C++, nome proposto por Rick Mascitti. Em C, ++ incrementa um valor, pelo que o nome sugere um C «incrementado» em vez de uma substituição completa.
A linguagem foi acrescentando ou reforçando funções virtuais, sobrecarga de funções e operadores, referências, constantes e verificação de tipos mais rigorosa. Em 1985, a primeira edição de The C++ Programming Language acompanhou a sua difusão para além do ambiente experimental dos Bell Labs.
A continuidade com C tinha grande valor estratégico. Os programadores podiam reutilizar conhecimentos, bibliotecas e ferramentas. Mas criou também um encargo duradouro: C++ teria de conciliar os seus novos mecanismos com uma herança concebida antes da própria linguagem.
Orientada a objetos, mas nunca apenas a objetos
A programação orientada a objetos cresceu rapidamente nas décadas de 1980 e 1990. C++ ajudou a popularizar classes, encapsulamento, herança e funções virtuais para polimorfismo dinâmico.
No entanto, descrever C++ simplesmente como «C com objetos» tornou-se rapidamente insuficiente. A linguagem nunca impôs um único modelo. Funções livres, tipos por valor, classes, herança, programação genérica e acesso direto aos recursos podem coexistir no mesmo programa.
Esta abordagem multiparadigma distingue C++ de linguagens construídas em torno de um modelo de objetos mais uniforme.
Zero-overhead abstractions
Uma expressão resume grande parte da filosofia de C++: zero-overhead abstractions.
As funcionalidades que não são utilizadas não devem impor um custo; as abstrações utilizadas não devem ser significativamente menos eficientes do que uma implementação manual equivalente bem escrita.
Isto não significa que todas as abstrações de C++ sejam literalmente gratuitas. Significa que a linguagem procura permitir conceitos de alto nível sem impor universalmente uma máquina virtual, um garbage collector ou um modelo de runtime mais pesado.
Esta ambição explica tanto a eficiência de C++ como parte da sua dificuldade. Manter representação, duração de vida e custos precisamente controláveis exige mais conceitos do que em ambientes que optam por esconder essas decisões.
RAII: usar a duração de vida como ferramenta
Construtores e destruidores conduziram a um dos idiomas mais característicos de C++: RAII, Resource Acquisition Is Initialization.
Um recurso fica associado à duração de vida de um objeto. O construtor pode adquiri-lo e o destruidor libertá-lo automaticamente quando o objeto sai do seu âmbito. Pode tratar-se de memória, mas também de um ficheiro, um bloqueio, uma ligação ou um identificador do sistema operativo.
RAII transforma a destruição determinística de objetos num mecanismo geral de gestão de recursos. Continua central no C++ moderno e ajuda a explicar por que razão as boas práticas atuais evitam frequentemente chamadas manuais dispersas a new e delete.
Templates e programação genérica
No final dos anos 1980 e início dos anos 1990, os templates deram outra dimensão a C++. Funções e tipos passaram a poder ser parametrizados por outros tipos, permitindo exprimir algoritmos e estruturas de dados de forma genérica.
Foi uma mudança decisiva. C++ deixou de ser apenas C com orientação a objetos e tornou-se também um dos grandes ambientes da programação genérica.
O trabalho de Alexander Stepanov e dos seus colaboradores conduziu à Standard Template Library, ou STL. A sua ideia fundamental consiste em separar os contentores dos algoritmos que operam sobre eles através de interfaces genéricas, em especial iteradores.
Contentores como vector e map podem assim utilizar algoritmos padrão de ordenação, pesquisa e transformação sem exigir uma versão específica de cada algoritmo para cada contentor.
A STL demonstrou que genericidade, reutilização, abstração e desempenho podem coexistir. Tornou-se uma parte fundamental da biblioteca padrão e alterou a forma idiomática de escrever C++.
1998: um padrão ISO
Com a multiplicação dos compiladores aumentou o risco de dialetos incompatíveis. A normalização internacional tornou-se indispensável.
Em 1998, C++ recebeu o primeiro padrão ISO completo. C++98 formalizou a linguagem e integrou a STL na biblioteca padrão. C++03 surgiu depois como revisão corretiva.
A normalização alterou também a governação. A evolução passou a decorrer através de um comité internacional, propostas, implementações e compromissos entre muitos participantes.
A estabilidade favoreceu a adoção industrial, mas nos anos 2000 consolidou-se também a reputação de C++ como linguagem difícil. Gestão manual da memória, ponteiros inválidos, fugas, sintaxe intrincada e diagnósticos de templates difíceis de ler eram críticas recorrentes.
C++11 e a era do C++ moderno
A grande viragem seguinte chegou em 2011. C++11 foi suficientemente importante para se falar habitualmente numa era de C++ moderno.
Entre as principais novidades encontram-se:
lambdas;
inferência de tipos com auto;
nullptr;
ciclos for baseados em intervalos;
smart pointers padrão;
rvalue references e move semantics;
constexpr;
variadic templates;
melhor suporte para concorrência.
Estas funcionalidades fizeram mais do que encurtar código: alteraram as práticas recomendadas.
A move semantics, por exemplo, permite transferir os recursos de um objeto que deixou de precisar deles em vez de os copiar. Smart pointers como std::unique_ptr e std::shared_ptr, combinados com RAII, tornam mais explícitas a propriedade e a duração de vida dos objetos.
O C++ moderno procura assim depender menos da gestão manual de cada recurso e mais de abstrações cujos custos e duração de vida continuam previsíveis.
Um ritmo de evolução mais regular
Depois de C++11, a linguagem adotou um ciclo de normalização de aproximadamente três anos.
C++14 aperfeiçoou muitos elementos de C++11. C++17 acrescentou ferramentas como std::optional, std::variant, std::filesystem e novas melhorias na programação genérica.
C++20 representou outro grande marco com concepts, ranges, coroutines e modules. Os concepts tornam mais explícitas as restrições sobre parâmetros genéricos; os ranges tornam muitas operações sobre sequências mais componíveis; as coroutines fornecem suporte da linguagem a determinados modelos assíncronos; os modules procuram, entre outros objetivos, melhorar a organização e a compilação relativamente ao modelo histórico de ficheiros de cabeçalho.
C++23 continuou esta evolução com novas melhorias na linguagem e na biblioteca. A estratégia geral mantém-se: modernizar C++ sem impor uma rutura brusca com a sua enorme base instalada.
Porque continua C++ tão complexo?
Várias causas reforçam-se mutuamente.
C++ preserva uma compatibilidade histórica considerável, suporta vários paradigmas e expõe detalhes de representação e duração de vida que outros ambientes escondem. Além disso, tem de funcionar tanto em aplicações comuns como em sistemas onde alguns bytes, alguns microssegundos ou a ausência de um runtime obrigatório fazem realmente diferença.
Esta liberdade significa também que existem frequentemente várias formas de resolver o mesmo problema. C++ antigo, C++ moderno e código escrito num estilo próximo de C podem coexistir na mesma base de código.
Bibliotecas e orientações modernas procuram, por isso, reduzir a parte mais perigosa desse espaço: contentores padrão em vez de arrays manuais quando adequado, RAII para recursos, smart pointers quando existe propriedade dinâmica e algoritmos e tipos expressivos em vez de gestão manual repetitiva.
C++, Java e compromissos diferentes
O aparecimento de Java nos anos 1990 ilustra outra resposta a alguns problemas encontrados pelos programadores C++. Java manteve uma sintaxe familiar, mas apoiou-se numa máquina virtual e em garbage collection e restringiu vários mecanismos de baixo nível.
C++ favorece o controlo direto da representação, a gestão determinística dos recursos e a ausência de custos obrigatórios por funcionalidades não utilizadas. Java aceita um ambiente de execução mais estruturado em troca de um modelo mais uniforme.
Nenhum destes compromissos é universalmente superior: as duas linguagens respondem, em parte, a restrições diferentes.
Onde C++ continua importante
Apesar de muitos concorrentes mais recentes, C++ continua importante quando contam o desempenho, a latência, o controlo da memória, a portabilidade nativa ou o acesso ao hardware.
É amplamente utilizado em motores de jogos, navegadores, bases de dados, ferramentas criativas, computação científica, sistemas embebidos, infraestruturas de baixa latência e bibliotecas utilizadas por outras linguagens.
A sua idade é simultaneamente uma limitação e uma vantagem. Décadas de compiladores, bibliotecas, conhecimento especializado e software industrial formam um ecossistema que não pode ser substituído de uma só vez.
Linguagens mais recentes, em especial Rust, oferecem compromissos diferentes em torno da segurança da memória e da programação de sistemas. Podem competir com C++ em algumas áreas sem tornar obsoleta a sua enorme base de software.
O que revela a história de C++
C++ não passou de forma linear de uma linguagem antiga para outra completamente redesenhada. Cresceu por camadas: C with Classes, orientação a objetos, templates, STL, normalização, C++ moderno e, depois, um ciclo regular de novos padrões.
Algumas destas camadas contribuem para a complexidade. Outras fornecem precisamente as abstrações que permitem substituir práticas antigas por código mais seguro e expressivo sem perder as propriedades que tornaram C++ valioso.
A sua longevidade assenta, portanto, num equilíbrio pouco habitual: mudar o suficiente para continuar relevante, mas não tanto que rompa com o mundo que já depende da linguagem.
Cronologia
1979: Bjarne Stroustrup inicia C with Classes nos Bell Labs.
1983: a linguagem recebe o nome C++.
1985: primeira edição de The C++ Programming Language e primeira difusão comercial.
Final dos anos 80–anos 90: desenvolvimento dos templates e da programação genérica.
Anos 90: a STL de Alexander Stepanov entra no processo de normalização.
1998: primeiro padrão ISO, C++98.
2003: revisão corretiva C++03.
2011: C++11 abre a era do C++ moderno.
2014: C++14.
2017: C++17.
2020: C++20 introduz, entre outras funcionalidades, concepts, ranges, coroutines e modules.
2023: C++23 prossegue a evolução da linguagem e da biblioteca.
Hoje: C++ continua a evoluir através da normalização ISO e permanece amplamente utilizado em software sensível ao desempenho.
Perguntas frequentes
C++ é simplesmente uma versão orientada a objetos de C?
Não. A orientação a objetos foi central na história inicial, mas templates, STL, RAII e programação genérica tornaram-se igualmente importantes. C++ é uma linguagem multiparadigma.
Porque mantém C++ compatibilidade com C?
C++ foi concebido como uma evolução pragmática de C e beneficiou do seu ecossistema. Essa continuidade facilitou a adoção. A compatibilidade não é, contudo, absoluta: código C válido não é necessariamente código C++ válido.
O que significa «C++ moderno»?
A expressão refere-se geralmente a práticas e funcionalidades associadas a C++11 e versões posteriores: utilização sistemática de RAII, contentores e algoritmos padrão, smart pointers, move semantics, lambdas e outras ferramentas para código mais expressivo e seguro.
Porque não exige C++ um garbage collector?
A linguagem favorece o controlo determinístico dos recursos e evita impor a todos os programas o custo de um coletor ou de um runtime específico. RAII, contentores e smart pointers automatizam grande parte da gestão sem garbage collection obrigatória.
C++ continua relevante perante Rust e linguagens mais recentes?
Sim. O seu ecossistema, desempenho, portabilidade nativa e enorme base de software existente continuam a ser vantagens importantes. Rust oferece garantias diferentes de segurança da memória e é uma alternativa relevante para alguns novos projetos de sistemas, mas as duas linguagens coexistem em muitos domínios.
Porque não elimina C++ mais funcionalidades antigas à medida que evolui?
Porque a compatibilidade com décadas de software tem grande valor para o ecossistema. O comité prefere frequentemente acrescentar mecanismos melhores e alterar as práticas recomendadas em vez de tornar abruptamente incompatíveis grandes quantidades de código existente.