C++ tem recebido uma quantidade enorme de críticas: É uma linguagem muito grande que leva um longo tempo para ser aprendida; a padronização tem demorado muito, o que torna difícil escrever código portável; linguagens mais recentes, notadamente Java, tem recebido mais atenção da imprensa especializada. Ainda assim, entre as linguagens que suportam o estilo orientado a objeto, C++ é de longe a linguagem mais usada, e seu uso continua crescendo rapidamente. Por quê?
Um pouco da complexidade do C++ foi herdada do C, ou resulta do próprio processo evolucionário da linguagem, mas, em sua maior parte, C++ é complexa porque é poderosa. Para um problema simples, qualquer linguagem serve; um problema difícil requer uma linguagem poderosa. Cada recurso do C++ existe porque comprovadamente é importante para alguma área da indústria de software. Com a padronização da linguagem quase concluída, os compiladores que implementam a maioria dos novos recursos padrões estão disponíveis para quase todas as plataformas.

Programadores do mundo real estão mais interessados na solução de problemas do que em linguagens. Uma linguagem de programação é um meio de se resolver um problema. Quando você usa a combinação correta de linguagens, e de recursos das linguagens, a solução para um problema se torna mais fácil de descrever e de implementar, e com melhores resultados. C++ continua sendo uma ferramenta essencial para os engenheiros de software, não porque alguém a considere a melhor linguagem possível, mas porque C++ é uma linguagem única, portável, que funciona melhor que qualquer outra alternativa em inúmeras áreas de aplicação. Nesse artigo eu vou explorar cada um dos pontos fortes do C++ e mostrar como você pode explorá-los em seus próprios projetos.

Porque usar C++?

C++ é uma linguagem de programação de uso geral, projetada para tornar a atividade de programação mais prazerosa para os programadores sérios. Bjarne Stroustrup, 1985.

Para vários casos, C++ não é a linguagem ideal. Você poderia preferir Tcl/Tk para escrever uma interface com usuário, SQL para acessos a bancos de dados relacionais, Java para programação de redes, ou Yacc para escrever um parse (analisador gramatical). C++ é usado porque funciona bem quando a linguagem ideal para o problema, por qualquer motivo, não está disponível, e porque se relaciona bem com outras bibliotecas e outras linguagens que você utilize para a solução global do problema.

Não é por acidente que você pode relacionar C++ com qualquer outra linguagem ou biblioteca. Você raramente vai encontrar um grande programa totalmente escrito em uma única linguagem, ou que não use bibliotecas de terceiros. Portanto, a integração fácil com outras linguagens e com outras bibliotecas é ponto chave no desenvolvimento de software.

Em muitos casos não há uma linguagem especializada, ideal, para a solução do problema. Algumas vezes porque tal linguagem simplesmente ainda não foi criada, outras vezes porque um interpretador especializado no problema poderia acrescentar um overhead indesejado. Quando você não pode obter a linguagem especializada ideal para uma parte específica do seu problema, uma biblioteca pode ser a solução adequada. C++ foi projetado tendo sempre em mente a utilização de bibliotecas, seus mais poderosos recursos são os que lhe ajudam a escrever bibliotecas portáveis, eficientes e de fácil utilização.

C++ é como uma cola.

A solução de qualquer grande problema depende da solução de vários problemas pequenos. Para alguns desses problemas pequenos você poderá encontrar uma linguagem especializada ou uma biblioteca otimamente adequada ao trabalho. Para o restante dos problemas você terá que escrever o seu próprio código, e então acoplá-lo às outras partes da solução global do problema.

Por exemplo, em uma aplicação comercial, você poderá usar SQL para interagir com um servidor de banco de dados, Postcript para as funções de impressão, e Tcl/Tk para interfaces com os usuários; e ainda ligar tudo isso a bibliotecas de aritmética de ponto fixo e de encriptação. Em um jogo de múltiplos competidores, você poderá usar applets Java na máquina cliente, VRML3D para descrever um mundo virtual, e Scheme para descrever as personagens do jogo; e ainda ligar tudo a bibliotecas para controle de rede e de multitarefas.

C++ é, por projeto, adequado a funcionar em conjunto com outras partes de um mesmo projeto de programação. Tem recursos especificamente projetados para chamar bibliotecas escritas em outras linguagens, e os projetistas das outras linguagens são meticulosos em torná-las conectáveis a programas escritos em C++. Isso também inclui recursos auxiliares para organizar grandes programas, e para impedir que as bibliotecas interfiram umas nas outras, ou mesmo no programa principal. Mais importante ainda, C++ não impõe um limite máximo ao tamanho do problema a ser resolvido, ou seja, na medida em que seu programa evolua e cresça, você não vai descobrir de repente que a linguagem já não funciona tão bem quanto na versão inicial do programa.

O recurso mais familiar do C++ para interface com outras linguagens é o seu subconjunto C. Qualquer coisa que você possa fazer em C pode também ser feita em C++; por conseqüência, qualquer interface projetada para C pode ser usada a partir de um programa em C++. De modo semelhante, a construção asm (...) lhe dá acesso a códigos de baixo nível e a bibliotecas que não fazem parte da interface C. Para chamar uma biblioteca C, ou para exportar interfaces C, você apenas inclui as declarações correspondentes no bloco extern "C" {...}.

Interpretadores são valiosos quando a semântica desejada é desconhecida até o momento da execução do programa, ou quando o ciclo de compilação e ligação é muito lento. Para contornar gargalos de peformance, muitos interpretadores permitem que você utilize subrotinas em C++. De fato, muitos interpretadores são escritos em C++, e seus recursos built-in são implementados dessa forma, ou seja, utilizando subrotinas em C++. Alguns outros interpretadores estão disponíveis sob a forma de bibliotecas, e permitem que você crie um objeto interpretador em seu próprio programa C++ quando for necessário. Tais interpretadores são denominados extensões de linguagem. Entre os melhores desse tipo estão Python, Korn Shell (ksh95), Tcl, e Scm (uma variante de Scheme).

Para ajudar a melhorar a organização do programa, os compiladores C++ mais recentes implementam o recurso namespace. Isso permite que você proteja seu programa do caos gerado pelos nomes globais das interfaces dos sistemas operacionais, das bibliotecas C, e mesmo de outras bibliotecas C++. namespace lhe permite agrupar todos os seus nomes globais (funções, classes e templates) em um escopo, ou em escopos, em separado, de forma que você pode combinar o uso de múltiplas bibliotecas, suas ou de terceiros, sem se preocupar com a colisão de nomes. Use esse recurso religiosamente.

C++ para exigências críticas de performance.

Algumas aplicações podem tolerar alguma degradação de eficiência na implementação, mas outras sequer podem admitir isso. No passado você era forçado a melhorar a performance rescrevendo as partes críticas do código, talvez até usando Assembler ou algum compilador Fortran otimizado. Com o C++ Padrão e com os modernos compiladores, isso não é mais necessário.

Cada recurso do C++ foi criteriosamente projetado para ser usado onde performance for uma exigência crítica. Mais precisamente, cada recurso é tão rápido quanto a solução equivalente em C. Assim uma função inline é tão rápida quanto uma macro C, e a chamada a uma função virtual é tão rápida quanto a consulta a uma tabela para selecionar a função a ser chamada.

O mecanismo de template do C++ pode realizar muitas operações em tempo de compilação e gerar resultados de consumo zero em tempo de execução (veja o Exemplo 1), e pode realizar os controles para expandir, em tempo de compilação, um algoritmo inline (veja Exemplo 2). Um código em C++ usando bibliotecas de alto nível, tal como Blitz++ (veja Recursos C++) e um bom compilador otimizado pode fazer frente ao Fortran na sua própria área de atuação: processamento de matrizes numéricas. É essa combinação de operações básicas eficientes com o poderoso mecanismo de template que faz do C++ uma boa escolha para a implementação das partes críticas de qualquer aplicação.

C++ para escrever bibliotecas.

Eu já mencionei que C++ tem recursos ideais para escrever bibliotecas eficientes, portáveis e de fácil utilização. Isso não é acidental. Para ser útil na elaboração de programas realmente grandes, uma linguagem deve facilitar a montagem de grandes programas a partir de peças menores, que oculta a complexidade do problema atrás de interfaces simples. Essas peças são bibliotecas.

Bibliotecas não são criadas apenas para reutilização de código. É bastante difícil extrair uma biblioteca de um programa e torná-la utilizável por outros programas, mesmo quando tal biblioteca foi escrita tendo em mente essa reutilização. O objetivo de boas interfaces de bibliotecas e tornar possível a compreensão de um grande programa. Um programa grande pode ser compreensível apenas se a complexidade das operações não ultrapassar as interfaces da bibliotecas que são utilizadas.

Parte da ocultação da complexidade consiste em ocultar o uso de técnicas avançadas para usuários iniciantes, que podem não compreendê-las. Em outras palavras, recursos da linguagem e técnicas de programação que sejam dominadas apenas por uns poucos programadores podem ser usadas para fazer bibliotecas melhores para todos. Isso é claramente evidente em algumas bibliotecas disponíveis atualmente, tais como a STL (Standard Template Library), que oculta técnicas avançadas, potencialmente incompreensíveis para muitos, atrás de interfaces simples.

Você pode usar essas bibliotecas em vários níveis. Elas contêm códigos para operações úteis, e você pode apenas chamá-las a partir de seu programa. Mas são também bons exemplos de projeto de interface e algumas vezes de implementação brilhante. Leia-as como literatura, para inspirar os seus próprios projetos, e publique suas próprias bibliotecas. Assim, outros vão aprender a partir delas e também ajudá-lo a melhorá-las.

Alerta

Sempre use o melhor e o mais avançado dos compiladores disponíveis. No passado, usar recursos muito avançados de uma linguagem era o mesmo que procurar por problemas. Isso não é mais verdade. Procure um compilador realmente up-to-date, e não aceite menos do que isso. Atualmente, compiladores baseados no front end de Edison Design Group (EDG) implementam os recursos avançados da linguagem de modo mais confiável (Veja Recursos C++).

Estude C++ um pouco a cada vez. Comece com o básico: variáveis, funções, controle do fluxo de execução. Você não precisa aprender C para depois aprender C++. C++ oferece modos melhores de se fazer as mesmas coisas que se pode fazer em C. Aprenda como C++ lida com a memória e como funcionam os construtores e os destrutores. Aprenda os recursos necessários para usar bibliotecas: como criar objetos, chamar funções membro, instanciar templates. Aprenda como escrever código à prova de exceções (nesse ponto C++ já lhe será uma ferramenta útil). Certifique-se de colocar todo o seu código em namespaces, desde o início.

Pode-se levar anos para aprender a fazer bons projetos de classes e templates. Comece então copiando os bons exemplos. Se você não acredita que seja difícil projetar uma classe string, tente encontrar uma que seja realmente boa. Aprenda sobre templates estudando o funcionamento de bibliotecas que as utilizem bem. A biblioteca standard do C++ oferece tanto bons exemplos (iterators, algoritmos e containers) quanto exemplos ruins (string). Até que você se torne um especialista em C++, evite o uso de sobrecarga de operadores, exceto quando incorporados a interface de bibliotecas que você esteja usando (como operator<< para ostream). Evite conversão automática de tipos, você vai renegá-las após colecionar problemas.

Não se deixe impressionar com herança e funções virtuais. São importantes, mas não tanto quanto alguns autores imaginam que sejam. C++ oferece outros modos, geralmente melhores, de expressar abstração. Em particular, suspeite de hierarquia de classes muito profundas, com muitos níveis de herança. Na STL, streambufs e locale usam herança com bom senso. Herança não precisa ser exposta na interface da biblioteca para que seja útil. Na STL, streambufs está oculta atrás de streams, e locale está oculta atrás de locales.

C++ tem sido chamada de uma linguagem orientada a objeto impura. Esqueça esse rótulo. C++ tem recursos para suportar programação orientada a objeto e também programação estruturada, e até programação sem qualquer estilo. Possui aritmética de pointers, operadores em nível de bit e o comando goto. Todos são úteis, depende do caso e do programador. Categorizações teóricas são muito interessantes para quem quer escrever sobre programação, não para quem quer escrever programas.

Não há substituto para o libro de Bjarne Stroustrup The C++ Programming Language, terceira edição (Addison-Wesley, 1997). Você pode começar com textos introdutórios, mas aprofunde-se aos poucos até ler Stroustrup. Use um bom guia de estilo, como Industrial Strength C++: Rules and Recommendations de Hericson e Nyquist (Prentice Hall, 1995), e colecione livros de dicas, como Effective C++: 50 Especific Ways to Improve Your Program and Design de Scott Meyers (Addison-Wesley, 1992). Aprenda o vocabulário padrão; muita gente começa com Design Patterns: Elements of Reusable Object-Oriented Software de Erich Gamma et al (Addison-Wesley, 1994). Lembre-se que tornar-se um bom projetista leva anos, não importa que linguagem você use para expressar suas idéias.

Acima de tudo, a principal habilidade a desenvolver é escrever código corretamente. Testes nunca conseguem ser completos, e algumas vezes são impossíveis (como testar código multitarefa em tempo real), portanto não há substituto para fazer certo. Você pode usar testes de programas para provar a presença de erros, mas nunca para provar sua ausência. A linguagem fornece algumas ferramentas que ajudam a prevenir erros; use todas elas. Acostume-se a certificar-se de que cada comando, ou bloco de comandos, faz exatamente o que se quer que faça.

Adote um estilo de programação do tipo contrato. Para cada interface, liste a pressuposições e as garantias que você de fato, realmente, não pode controlar no código. Um bom programador pode ser milhares de vezes mais produtivo do que um programador medíocre; essa habilidade é uma grande parte da diferença.

Durante os primeiros dois anos em que estiver usando C++, freqüentemente você descobrirá maneiras melhores de fazer alguma coisa que já está feita, o que o levará a rescrever centenas de linhas de códigos. Ótimo. Isso é evolução e aprofundamento. Não relute em abandonar um código já escrito e substituí-lo por um outro melhor. O que é valioso em uma biblioteca não é propriamente o código, mas a qualidade da solução.

Não tente usar uma única linguagem para tudo; aprenda outras linguagens e capacite-se a combiná-las para explorar seus pontos fortes individuais. Evite linguagens proprietárias, não portáveis. Estas vão desaparecer rapidamente.

As ferramentas que você souber usar vão determinar o nível dos projetos nos quais você pode se envolver. Os melhores projetos estão reservados aos que sabem usar melhor as melhores ferramentas.

Conclusão

C++ é uma linguagem entre muitas, mas sua combinação impar de pontos fortes a torna uma ferramenta essencial para engenheiros de software sérios. Embora você possa levar anos para aprender C++ completamente, você pode beneficiar-se de seus recursos imediatamente.

xemplo 1: Cálculo de raiz quadrada em tempo de compilação: ceil(sqrt(N)).


Código:

//root.h
template <int Size, int Low = 1, int High = Size>
  struct Root;
template <int Size, int Mid>
  struct Root<Size,Mid,Mid> {
    static const int root = Mid;
  };
template <int Size, int Low, int High>
  struct Root {
    static const int mean = (Low + High)/2;
    static const bool down = (mean * mean >= Size);
    static const int root = Root<Size,
          (down ? Low : mean+1),
          (down ? mean : High) >::root;
  };
// compute sqrt, use it for static table size
int table[Root<N>::root]
Exemplo 2: Expansão inline do produto do vetor dot
//Given a forward declaration:
template <int Dim, class T>
  struct dot_class;
// a specialized base case for recursion:
template <class T>
  struct dot_class<1,T> {
    static inline T dot(const T* a, const T* b)
      { return *a * *b; }
  };
// the recursive template:
template <int Dim, class T>
  struct dot_class {
    static inline T dot(const T* a, const T* b)
      { return dot_class<Dim-1,T>::dot(a+1,b+1) +
              *a * *b }
  };
// ... and some syntactic sugar:
template <int Dim, class T>
  inline T dot(const T* a, cont T* b)
    { return dot_class<Dim,T>::dot(a, b); }
// Then
int produt = dot<3>(a, b);
// results in the same (near-)optimal code as
int product = a[0]*b[0] + a[1]*b[1] + a[2]*b[2]
Recursos C++