Unidade II- extraido do site http://www.ccppbrasil.org/planet/

By elg003
Entrevista ++ : Bjarne Stroustrup fala sobre a evolução das linguagens de programação
De tempos em tempos, um salto evolucionário promove o rápido avanço e a reformulação de todo o campo da engenharia. Um movimento assim ocorreu no desenvolvimento de software com o lançamento da linguagem de programação C++. E não foi algo inerente à linguagem propriamente dita: linguagens orientadas a objeto, como o Simula67 e o Smalltalk, já existiam antes do C++. Mas, como o C++ foi criado a partir da linguagem de programação C (sendo capaz de compilar os programas em C já existentes), conseguiu trazer as abstrações do pensamento orientado a objeto para um público muito mais amplo.
O C++ inspirou muitas idéias associadas ao design e ao desenvolvimento de software, desde padrões de design até metaprogramação. E, devido à sua portabilidade entre plataformas de hardware e expressividade em nível inferior, o C++ terá certamente um papel fundamental em um mundo com dispositivos de hardware menores e mais rápidos.
Recentemente, tive o prazer de conversar com Bjarne Stroustrup, o criador do C++, sobre vários assuntos, desde suas idéias sobre linguagens até as tendências gerais do setor e sua lista pessoal de leitura. Muitas das perguntas foram sugeridas pelos leitores no meu blog, então agradeço a todos os que contribuíram. E, é claro, agradeço a Bjarne.
Idéias sobre linguagens
Howard Dierking Por que as pessoas têm uma ligação tão profunda com as linguagens de programação, a ponto de se formarem comunidades de fanáticos por linguagens?
Bjarne Stroustrup Você deveria perguntar isso a um psicólogo, um sociólogo, ou até mesmo a um economista, e não a um cientista da computação! Meu palpite é que as linguagens que usamos para expressar nossas idéias tornam-se parte de nós, tanto que, se você só conhece uma linguagem, os partidários de outras linguagens podem representar uma ameaça pessoal. Nesse caso, a solução parece ser conhecer também outras linguagens. Não acredito que seja possível atuar profissionalmente na área de software conhecendo apenas uma linguagem de programação. Também pode haver um motivo econômico: embora os conhecimentos básicos transcendam os limites das linguagens de programação, o mesmo não se aplica a várias habilidades práticas. Então, se eu conheço apenas a linguagem X e seus conjuntos de ferramentas, mas você quer discutir a linguagem Y e seus conjuntos de ferramentas, você está ameaçando o meu meio de vida. Mais uma vez, parece que a solução é conhecer várias linguagens e conjuntos de ferramentas (e ter uma base sólida nos fundamentos da área). Infelizmente, as soluções que sugiro não levam em conta o fato de que a maioria das pessoas tem muito pouco tempo livre, depois de dar conta de todos os afazeres. Mas isso não é desculpa para o fanatismo.
HD Qual deveria ser o papel do IDE no desenvolvimento de software? De que forma o IDE deveria dar suporte a uma linguagem?
BS Não sou um usuário intensivo de IDE. Gosto de um editor de IDE com boa capacidade de resposta e compreensão da minha linguagem, mas também quero ser capaz de trabalhar sem um IDE. Talvez eu tivesse outra opinião se houvesse um bom IDE disponível universalmente — que fosse, na verdade, uma parte da linguagem, ou vice-versa . O meu desejo de portabilidade interfere nessa questão. Com o C++, quero ser capaz de entender o meu sistema somente a partir do código-fonte nos arquivos de origem. Rejeito energicamente os mecanismos de IDE envolvendo transformações ou gerações que não podem ser representadas por código adequado ao consumo humano.
HD Você vê o ruído ou a legibilidade como um problema nas atuais linguagens de finalidade geral? Em caso positivo, qual é a solução?
BS Seria ótimo ter uma sintaxe mais simples, mas suspeito que a maioria das pessoas, quando reclama da legibilidade, não se refere ao texto em si, mas sim à complexidade do que é expresso. Muitas pessoas esperam explorar qualquer programa, escrito em qualquer linguagem, e — com alguma ajuda de uma estrutura de suporte online — entender todos os construtores usados para expressar o programa e toda a lógica do programa em si. Compare isso com a forma como entendemos e usamos os idiomas. Você esperaria entender um soneto de Shakespeare sem qualquer informação de referência? E quanto ao Beowulf no inglês arcaico original? Talvez esperemos demais das linguagens de programação. Qualquer linguagem capaz de expressar todos os elementos necessários a uma vastidão de áreas de aplicativos poderia ser considerada desnecessariamente complexa para um determinado aplicativo, mas na verdade precisa lidar com um conjunto de aplicativos praticamente ilimitado. As linguagens específicas de domínio podem ajudar em casos específicos, mas então precisamos lidar com as complexidades de muitas linguagens e de suas interações.
HD Como uma linguagem de finalidade geral deve dar suporte a idéias para o design de aplicativos, como a programação de componentes e serviços?
BS Uma linguagem de finalidade geral deveria dar suporte à escrita de bibliotecas capazes de expressar noções gerais e específicas de aplicativos, dar suporte à criação de ferramentas e fornecer o elemento de ligação necessário para conectar partes distintas de um aplicativo. Para fazer isso, a linguagem precisa de flexibilidade, um sistema de tipos expressivo, um bom desempenho básico e estabilidade de longo prazo.
HD A expedição múltipla é positiva?
BS Sim. Uma linguagem de programação convencional de expedição única orientada a objeto (como Simula, C++, Smalltalk, Java e C#) não pode expressar com elegância operações simples, como multiplicar números ou encontrar a interseção entre duas formas, nas quais os tipos exatos não são conhecidos até o runtime. O código resultante (que depende da expedição dupla, do padrão do visitante e assim por diante) é lento e não tão fácil de manter como gostaríamos. As linguagens que oferecem suporte à expedição múltipla em runtime (como Dylan e CLOS) apresentam melhor desempenho e as linguagens (como o C++) que oferecem suporte em tempo de compilação podem ajudar um pouco. Ano passado, junto com alguns alunos meus, publiquei um artigo de pesquisa sobre como acrescentar corretamente a expedição múltipla ao C++. O código resultante para casos de uso com expedição múltipla é menor, mais simples, usa menos memória e é executado com mais rapidez do que todas as soluções alternativas que já vimos (veja a Figura 2). No entanto, esse trabalho chegou tarde demais para o C++0x. Você encontra um artigo sobre isso em research.att.com/~bs/multimethods.pdf.
HD O que você acha da atual covariância/contravariância no C++? Você espera uma mudança do comportamento atual nas futuras versões da linguagem?
BS Na verdade, não. Nessa área, acho que o C++ faz o que deveria fazer. Você está pensando em exemplos como converter um vector<Apple> em um vector<Fruit>? Isso é extremamente inseguro, a não ser que o vector<Fruit> seja imutável (ou você poderia adicionar Orange a ele). Não acho que a verificação implícita de tipos em runtime seja uma boa abordagem para esse tipo de problema.
HD O que você acha de tornar a transmissão de mensagens um recurso fundamental das linguagens, em oposição à chamada de métodos?
BS Gosto da transmissão explícita de mensagens, mas só a utilizei há muito tempo atrás e no contexto de sistemas distribuídos. Em uma visão realista, para o trabalho em grande escala, precisaríamos aperfeiçoar muito o suporte de linguagens e ferramentas para a transmissão de mensagens. Não acredito que isso precise ser feito, mas posso estar errado. Muitos problemas associados ao compartilhamento e ao bloqueio desaparecem se você conta com mensagens e filas de mensagens. Seria ótimo se houvesse uma biblioteca padrão em C++ para isso, e talvez haja mesmo (daqui a alguns anos).
HD Existe um conflito inerente ao uso de linguagens de finalidade geral para dar suporte a um público amplo e diversificado e, ao mesmo tempo, para incentivar melhorias na expressividade do código e na elegância do design? Qual o papel da linguagem no suporte a esses últimos elementos?
BS Acho que existe, no sentido de que você pode alcançar a elegância por meio da especialização. Além disso, se você tem um público restrito (uma comunidade de usuários), pode limitar-se somente às suas preferências. Também é possível afastar-se das notações e dos conceitos convencionais, caso você exerça algum tipo de controle sobre a comunidade de usuários (dizendo algo como “você precisa aprofundar-se na teoria de tipos para usar isto”). Uma linguagem de finalidade geral é restrita pela necessidade de dar suporte a uma ampla gama de usos, e também pela necessidade de ser ensinada a vastos grupos de pessoas, com uma grande variedade de pressuposições e formações educacionais (o básico precisa ser utilizável por um estudante de ensino médio com um professor ruim).
Então, acho que uma linguagem de finalidade geral pode incentivar a elegância, na medida em que códigos elegantes possam ser expressos nela. No caso do C++, é possível escrever código muito elegante. Por fazer parte de uma linguagem de finalidade geral e amplamente disponível, seus exemplos podem chegar às mãos de milhões de pessoas, em artigos e livros que poderão ser lidos por milhões. Nenhuma linguagem especializada — por mais elegante que seja — conta com opções assim.
HD Será que demos ênfase demais à arquitetura?
BS Não, pelo menos da forma como eu definiria a arquitetura. Pelo contrário, há muito pouca ênfase na arquitetura e muito código ruim, com pouca compreensão dos princípios estruturais. Suspeito que um dos principais problemas da arquitetura é que muitos programadores têm apenas uma vaga idéia do que faz um código ser bom. Ser capaz de reconhecê-lo ao vê-lo não é suficiente. Ter regras determinando o que não fazer também não é suficiente. Precisamos de regras prescritivas articuladas.
HD A comunidade do código aberto ajudou, atrapalhou ou não fez diferença na qualidade, no design e no profissionalismo da área de software?
BS É uma pergunta muito difícil. Já vi casos em que ajudou (aumentou a qualidade do código e o nível de profissionalismo das pessoas envolvidas), casos em que atrapalhou (introduziu hábitos e atitudes inadequados) e muitos casos em que eu não seria capaz de julgar.
Não tenho como saber quais foram os efeitos sobre a comunidade como um todo ou o que teria acontecido se o volume de trabalho com código aberto fosse maior ou menor. Simplesmente, a comunidade é grande demais para que eu possa avaliar isso.
Tendências das linguagens
HD Você percebe uma tendência forte no sentido das linguagens dinâmicas para o futuro próximo?
BS Na verdade, não. Acho que as pessoas estão, com muita freqüência, comparando coisas muito parecidas. Não acredito que tenhamos escolha entre as linguagens estáticas e dinâmicas em geral e, além disso, não acho que as linguagens se encaixem claramente nessas duas categorias: a maioria, se não todas as linguagens dinâmicas, tem aspectos estaticamente determinados, e todas as principais linguagens estáticas podem realizar tarefas que exigem a determinação em runtime do significado dos valores. Claro que existem modismos, e eu não posso avaliar isso, mas acho que muitas escolhas reais de linguagens são feitas racionalmente, com base nos requisitos de um aplicativo e de uma área de aplicativos e/ou nas habilidades dos desenvolvedores disponíveis. Por exemplo, eu não tentaria implementar um runtime Java, digamos, em Ruby, nem expressar uma linguagem de simulação altamente interativa em C++ (já no caso da implementação, seria o contrário).
HD Você consideraria positivo remover definitivamente toda a existência de void*/variant/object/etc.?
BS Acho que, em tese, seria uma boa idéia livrar-se de todas as opções genéricas, mas na realidade só conseguiríamos fazer isso com uma classificação completa de tudo o que quiséssemos expressar, para que pudéssemos ser sempre mais específicos. Por exemplo, nunca vi uma função real capaz de fazer alguma coisa com todos os objetos. Se você faz alguma coisa, sempre faz alguma suposição sobre o que está manipulando (uma função pura de encaminhamento é o mais perto que consigo chegar de uma exceção a isso). O trabalho atual nos conceitos do mundo do C++ (restrições e requisitos de algoritmos genéricos) ajudará, mas não acredito que possamos ficar sem alguns mecanismos realmente gerais para expressar “alguma coisa, mas não sei exatamente o quê”. Isso é inevitável quando lidamos com necessidades imprevistas.
HD Com o retorno da tendência das linguagens com tipos flexíveis, será que devemos voltar a pensar em notações em húngaro?
BS Não tenho certeza da existência dessa tendência, embora provavelmente haja um aumento na fração do total de trabalho adequada às linguagens com tipos flexíveis. Em outras palavras, talvez o uso de linguagens com tipos estáticos ainda esteja aumentando (e acho que está) enquanto o uso de linguagens com tipos flexíveis aumenta mais rápido. E não, esqueça o húngaro. O húngaro é uma péssima idéia. O código-fonte deve refletir o significado de um programa, e não simular um sistema de tipos. Se você sente mesmo a necessidade de recorrer ao húngaro, provavelmente está usando uma linguagem inadequada ao seu aplicativo.
HD Em 2000, você fez uma apresentação chamada “C++: A New Language for a New Millennium” (C++: uma nova linguagem para um novo milênio) e lançou o conceito de um Wrap<T>. Isso é basicamente AOP (programação orientada a aspectos). O que você acha da AOP (de forma geral) e da sua formalização do padrão Wrap<T> (pointcuts, advice e assim por diante)?
BS Não dediquei tempo suficiente à AOP para dar uma resposta concreta. Gosto que a composição (especialmente a não-intrusiva) tenha suporte em uma linguagem (em oposição às opções “sem suporte” e “com suporte de uma ferramenta”). Ferramentas extralingüísticas e cadeias de ferramentas não-padrão me preocupam. Os modelos em C++ têm obtido um incrível sucesso na composição não-intrusiva — basta mencionar a STL (Standard Template Library) e alguns usos na programação de sistemas incorporados. A capacidade de combinar idéias sem precisar encaixá-las em uma hierarquia rígida ou pré-projetada é um ponto forte significativo. No entanto, também tem sido difícil de gerenciar para alguns desenvolvedores e mantenedores. Além de ser pesada em termos de notações. O C++0x inclui tentativas de lidar com isso sem impacto sobre a flexibilidade e o desempenho.
HD Determinadas características da linguagem C++ têm o potencial de criar algumas conseqüências indesejadas desagradáveis (como as macros). Que outras conseqüências indesejadas você gostaria que tivessem a ambigüidade removida no C++ ou nas linguagens modernas em geral?
BS Na verdade, eu já sabia que as macros eram desagradáveis quando comecei a usar C. Só que, assim como a maioria das pessoas, subestimei sua inconveniência e a abrangência das suas influências negativas. Provavelmente, o uso abrangente de macros no C é o principal motivo pelo qual não tínhamos excelentes ambientes de desenvolvimento em C++ há uma década. Também há formas muito confusas e em quantidade excessiva para inicializar objetos em C++. Espero solucionar isso com um mecanismo uniforme no C++0x. Já mencionei os modelos na minha resposta à pergunta anterior. Eles são o maior sucesso do C++ recente (posterior a 1985), mas seu sucesso distorceu a linguagem — mais uma vez, partes do C++0x se destinam a lidar com isso.
Muitos problemas secundários e nem tão secundários que descobrimos ao longo dos anos não podem ser solucionados no C++ por motivos relacionados à compatibilidade. Por exemplo, a sintaxe declarativa é uma complicação desnecessária. Praticamente qualquer notação linear seria melhor. Muitos padrões também estão errados: os construtores não deveriam ser conversões por padrão, os nomes não deveriam ser acessíveis a partir de outros arquivos de origem por padrão e assim por diante. A ausência de controle sobre o vinculador tem sido uma fonte constante de problemas. Os implementadores, especificamente, parecem gostar de fornecer recursos semelhantes em formas incompatíveis.
Mas também tivemos surpresas positivas. A mais espetacular foi o uso abrangente dos destruidores nas técnicas relacionadas ao gerenciamento de recursos e ao tratamento de erros (usando exceções). Eu sabia que os destruidores eram uma boa idéia — afinal, é preciso reverter o efeito dos construtores — mas não tinha percebido como eles teriam um papel central no bom uso do C++.
HD Você fez no seu site o seguinte comentário: “Acho que deveríamos buscar a elegância nos aplicativos criados, e não nas linguagens propriamente ditas.” A tendência das DSLs (linguagens específicas de domínio) é uma convergência nesse sentido?
BS É, tenho quase certeza. Geralmente é uma tentativa nessa direção. Às vezes, até funciona.
HD O que você acha das DSLs em geral? Como você imagina o relacionamento entre as DSLs e as linguagens de finalidade geral?
BS Eu me preocupo com a grande quantidade de linguagens projetadas, implementadas, lançadas com grande estardalhaço e depois esquecidas, sem impacto significativo. Durante sua longa fase de desenvolvimento, que pode levar anos, uma nova linguagem consome muitos recursos sem gerar, essencialmente, nenhum retorno. Escrevi um artigo sobre esse fenômeno chamado “A Rationale for Semantically Enhanced Library Languages” (A lógica das linguagens com bibliotecas de semântica avançada, research.att.com/~bs/SELLrationale.pdf). Defendo o uso de bibliotecas, possivelmente com o suporte de ferramentas, e de uma linguagem de finalidade geral.
Acho que uma DSL deveria ser o último recurso, e não o primeiro. Se possível, a DSL deveria estar solidamente enraizada em uma linguagem de finalidade geral e em cadeias de ferramentas padrão. Uma DSL precisa de uma linguagem de finalidade geral (ou pelo menos de uma linguagem de programação de sistemas) para sua implementação e para a implementação de seus primitivos de runtime. Acho que seria melhor se uma DSL estivesse conscientemente e firmemente emparelhada com pelo menos uma linguagem de finalidade geral, facilitando o acréscimo de novos recursos com o uso de bibliotecas escritas nessa linguagem. Logicamente, um profissional deve dominar várias linguagens, mas imagino se a soma da complexidade de diversas DSLs não seria grande demais, a ponto de tornar-se um problema. Além disso, muitas DSLs, ou a maioria delas, parecem “querer” tornar-se linguagens de finalidade geral.
HD Você mencionou que muitas construções em C++ foram intencionalmente tornadas ambíguas devido às várias definições em hardwares diferentes. Você percebe avanços na interoperabilidade capazes de remover a ambigüidade de algumas dessas construções?
BS “Ambíguas” não é a palavra certa. Aspectos demais foram deixados indefinidos ou a serem definidos na implementação. Suspeito que, se pudesse redefinir o C++ a partir do zero, ele não teria comportamentos indefinidos e teria muito menos comportamentos definidos na implementação. Só que não tenho uma máquina do tempo e simplesmente não é possível interromper centenas de milhares de linhas de código por conta de algumas resoluções tomadas agora.
Metodologia e práticas recomendadas
HD Que metodologia processual você tende a usar e ensinar?
BS Identificar os principais conceitos do aplicativo, identificar bibliotecas úteis, criar novas bibliotecas para dar suporte ao conceito do aplicativo, experimentar idéias com antecedência, fazer a integração com antecedência, testar com antecedência e sempre, usar o material de documentação e os tutoriais como ferramentas de design e criar programas maiores a partir de outros menores (fazendo a iteração ao longo do processo). Deve ser óbvio que agora estou concentrado em projetos relativamente pequenos.
HD Você percebe uma interseção ou uma afinidade entre uma linguagem e uma metodologia de desenvolvimento?
BS Acho que sim, na medida em que o design de bibliotecas é visto como uma técnica de design/desenvolvimento. O foco em recursos de nível cada vez mais elevado (mais próximo ao aplicativo) com a criação de bibliotecas transfere os requisitos para a linguagem. Não quero exagerar neste ponto, mas não acredito que seja possível ter uma única metodologia de desenvolvimento para (digamos) COBOL, C, Java, C++ e Python e esperar obter mais do que um suporte mínimo de cada linguagem.
HD Quais são suas regras básicas pessoais para a criação de software?
BS Foco nos conceitos principais; foco em suas interfaces; foco no gerenciamento dos recursos (memória, arquivos, bloqueios e assim por diante); foco no tratamento de erros. O design de boas constantes para classes e a RAII (aquisição de recurso é inicialização) são técnicas fundamentais.
HD Fala-se muito sobre o método Agile. O que o Agile significa para você? O C++ oferece suporte ao Agile?
BS Não uso esse termo, acho que é muito vago. É claro que o C++ oferece suporte ao Agile — seja qual for seu significado.
Um olhar para o futuro
HD Como uma linguagem pode evoluir para dar suporte a recursos avançados como modelos, eventos dinâmicos e escrita automática de código, e ao mesmo tempo permanecer acessível aos principiantes?
BS Não sei. Acho que não há uma resposta geral. Novos recursos podem ser importantes, desde que ofereçam suporte a técnicas mais eficazes no contexto da linguagem. No entanto, a estabilidade é essencial: um dos motivos da solidez contínua do C e do C++ é o cuidado dos comitês de padrões em assegurar que códigos antigos (muitas vezes, de décadas atrás) continuem válidos e em realizar uma integração suave dos novos recursos. Isso não é nada fácil, e a introdução de novos recursos nem sempre funciona. Muitas vezes, as dificuldades dos iniciantes são ignoradas pelos membros dos comitês de padrões. Alguns dos principais recursos planejados para o C++0x, como a inicialização uniforme, a palavra-chave automática (para deduzir um tipo de variável do seu inicializador) e conceitos como a verificação dos requisitos de argumentos de modelos devem servir para tornar a linguagem mais fácil de usar pelos não-especialistas.
HD Você vê os metadados de linguagem como uma base essencial para as futuras linguagens de programação?
BS Não. Pessoalmente, sinto-me desconfortável com usos não-triviais de metadados.
HD Você prevê para o futuro uma transição fundamental para a simultaneidade, caso as CPUs passem a ter um número cada vez maior de núcleos? Como o desafio da simultaneidade seria enfrentado pelo C++0x?
BS O C++0x fornece o básico: um modelo de computador adequado ao multithreading, um conjunto de primitivos de nível inferior para a criação de bibliotecas e uma API de biblioteca de threads e bloqueios. Eu gostaria muito que houvesse ainda mais recursos (e provavelmente haverá, nos próximos anos), especialmente um modelo de simultaneidade mais simples e de nível mais elevado, baseado em pools de threads, futuros e filas de mensagens. Precisamos encontrar formas automáticas ou semi-automáticas de difundir uma computação por vários processadores e localizar atividades nesses processadores. Há muito trabalho a ser feito nessa área — e muito em C++ — mas ainda não há um modelo dominante. Entre os exemplos, estão o STAPL, da Universidade Texas A&M, e o TBB, da Intel.
HD O que você acha da GPGPU (computação de finalidade geral em unidades de processamento gráfico)?
BS Assustadora, mas não tenho a experiência prática necessária para comentar o assunto além do óbvio: que é preciso ter muita habilidade para utilizar um processador de finalidade especial.
HD Você prevê que a HPC (computação de alto desempenho) se tornará, por fim, transparente para os programadores? E ainda, essa seria uma meta para uma linguagem ou para uma biblioteca ou estrutura?
BS Transparente, até certo ponto. Não acho que a simultaneidade possa ou deva ser completamente transparente. Antes de tudo, o tratamento de erros pode ser muito diferente, dependendo da disponibilidade de processadores, da memória compartilhada (ou não), da distribuição (ou não) e da latência. Aproximar-se da transparência, onde isso for adequado, é e continuará sendo uma meta das novas linguagens, dos novos recursos de linguagens e das novas bibliotecas (minhas favoritas). Essas últimas são viáveis somente se a linguagem subjacente fornecer as garantias básicas necessárias, como um modelo de computador e um conjunto de primitivos de nível muito baixo. O C++0x fará isso.
HD Como vai o design do C++0x?
BS Estamos quase terminando — finalmente! Pelo menos, espero que sim, pois nada está garantido até que a votação seja realizada e, daqui para o final, os ânimos ficam exaltados. O plano atual consiste em votarmos o novo padrão completo para comentário público em junho, para que tenhamos um novo padrão formal de 12 a 18 meses depois. É claro que eu poderia escrever um livro sobre o assunto: como se cria um padrão, o que deve estar contido nos princípios orientadores e o que exatamente o constitui. Praticamente já fiz isso. Veja o meu artigo para a HOPL, “Evolving a language in and for the real world: C++ 1991-2006″ (A evolução de uma linguagem no mundo real e para ele: C++ 1991-2006, disponível em research.att.com/~bs/hopl-almost-final.pdf) e qualquer publicação com C++0x no título nas minhas home pages. Se você gosta de sofrer, pode procurar “WG21″ na Web e encontrará todos os artigos do comitê de padrões ISO do C++ (inclusive todas as propostas). Se isso não servir para mais nada, pelo menos o convencerá de que é muito trabalho. É difícil aperfeiçoar uma linguagem de programação amplamente utilizada, principalmente uma que está na camada de implementação de uma infinidade de ferramentas, linguagens e aplicativos. Você também pode encontrar alguns vídeos, comigo e com outras pessoas, online e na minha página sobre o C++.
Livros e telefones
HD O que você está lendo no momento?
BS Na parte de material técnico, estou relendo Hennessy e Patterson para relembrar a área de arquitetura computacional. Também estou tentando coletar artigos que ajudem as pessoas a escrever código melhor (para um curso que estou ministrando). Encontrar esse tipo de artigo é muito mais difícil do que imaginei. Os artigos acadêmicos tendem a ser muito especializados. Já os não-acadêmicos prometem muito, mas trazem poucas evidências concretas (aceito sugestões). E, claro, há um fluxo constante de documentos relacionados à padronização do C++. Eu estava prestes a reler Knuth, mas alguém levou meu volume III, então isso terá que esperar. Por diversão, estou relendo um pouco da série de Aubrey e Maturin, de O’Brian. Estou tentando atualizar a minha compreensão científica, portanto acabo de sair de uma “overdose” de Richard Dawkins. Também terminei recentemente o livro de Rodger, Command of the Ocean: A Naval History of Britain, 1649-1815 (O comando do oceano: uma história naval da Grã-Bretanha, 1649-1815), daí a releitura de O’Brian.
HD Você disse: “Sempre desejei que meu computador fosse tão fácil de usar quanto meu telefone. O meu desejo se tornou realidade, porque não consigo mais descobrir como usar meu telefone.” Você tem um smartphone? E isso já ficou um pouco mais fácil?
BS Não sou um grande fã de telefones. Prefiro a comunicação pessoal e, se isso não for possível, a palavra escrita. Nem mesmo o telefone mais sofisticado se compara a um email bem escrito. Tenho um slim phone que cabe no meu bolso e estou longe de usar todos os seus recursos. Eu trocaria praticamente qualquer recurso por qualidade de som e confiabilidade. Para ser justo, as interfaces do usuário tendem a ser muito melhores hoje do que eram quando fiz esse comentário.
Howard Dierking é editor-chefe da MSDN Magazine.

Mutable, O legado

Continuando nosso artigo mutable, que raios é isso? agora irei demonstrar um segunda maneira de “burlar” o mesmo método const sem usar atributos mutáveis (mutable).

Temos a seguinte interface de exemplo, Entity (pure abstract)

class Entity
{
  public:
    virtual void logic() = 0;
    virtual void draw() const = 0;
};

Note que classes como a acima são muito comuns em vários projetos

Agora a classe Player que implementa Entity

class Player : public Entity
{
  public:
    void logic()
    {
    }

    void draw() const
    {
       mDrawCount++;

      // Erro! pois é um método const
    }

  private:
     std::size_t mDrawCount;
};

Mais skhaz porque não controlar isso no método logic que não é const? eu tenho bons motivos para isso, um deles é se você implementar um sistema de ticks como descrito no artigo Controle de velocidade de jogo com ticks onde quase sempre o método draw não será chamado no mesmo ciclo que logic.

ponteiro e const_cast os “vilões”
Com o uso do cast const_cast podemos burlar o atributo declarado como mutable (assim como para const e volatile) para não constantes.

void Player::draw() const
{
  const_cast<player *>(this)->mDrawCount++;

  // work fine =D
}

Quando o ponteiro nulo não é inválido

Coding HorrorExiste coisa mais prazerosa do que admitir um erro que foi cometido na mesma semana? Existe: quando você sabia que estava certo, mas resolveu usar o senso comum por falta de provas.

Pois bem. O mesmo amigo que me recomendou que escrevesse sobre o assunto do ponteiro nulo achou um livro sobre armadilhas em C com um exemplo que demonstra exatamente o contrário: dependendo da plataforma, ponteiros nulos são sim válidos.

Nesse caso, se tratava de um programa que iria rodar em um microprocessador, daqueles que o DQ costuma programar. Pois bem. Quando o dito cujo ligava era necessário chamar uma rotina que estava localizada exatamente no endereço 0. Para fazer isso, o código era o seguinte:

( * (void(*)()) 0 ) ();

Nada mais simples: um cast do endereço 0 (apesar de normalmente inválido, 0 pode ser convertido para endereço) para ponteiro de função que não recebe parâmetros e não retorna nada, seguido de deferência (“o apontado de”) e chamada (a dupla final de parênteses).

(* (void(*)()) 0 ) ();

É bem o que o autor diz depois de jogar esta expressão: “expressions like these strike terror into the hearts of C programmers”. É lógico que isso não é bem verdade para as pessoas que acompanham este blogue =)

Códigos de entrevista – o ponteiro nulo

Bom, parece que o “mother-fucker” wordpress ferrou com meu artigo sobre o Houaiss. Enquanto eu choro as pitangas aqui vai um outro artigo um pouco mais simples, mas igualmente interessante.

“Wanderley, tenho umas sugestões para teu blog.
A primeira:
Que tal analisar o código abaixo e dizer se compila ou não. Se não compilar, explicar porquê não compila. Se compilar, o que acontecerá e por quê.”

O código é o que veremos abaixo:

#include <stdio.h>
#include <stdlib.h>

void func()
{
	*(int *)0 = 0;
	return 0;
}

int main(int argc, char **argv)
{
	func();
	return 0;
}

Bem, para testar a compilação basta compilar. Porém, se estivermos em uma entrevista, geralmente não existe nenhum compilador em um raio de uma sala de reunião senão seu próprio cérebro.

E é nessas horas que os entrevistadores testam se você tem um bom cérebro ou um bom currículo.

Por isso, vamos analisar passo a passo cada bloco de código e entender o que pode estar errado. Se não encontrarmos, iremos supor que está tudo certo.

#include <stdio.h>
#include <stdlib.h>

Dois includes padrões, ultranormal, nada de errado aqui.

void func()
{
	*(int *)0 = 0;
	return 0;
}

Duas ressalvas aqui: a primeira quanto ao retorno da função é void, porém a função retorna um inteiro. Na linguagem C, isso funciona, no máximo um warning do compilador. Em C++, isso é erro brabo de tipagem.

A segunda ressalva diz respeito à linha obscura, sintaticamente correta, mas cuja semântica iremos guardar para o final, já que ainda falta o main para analisar.

int main(int argc, char **argv)
{
	func();
	return 0;
}

A clássica função inicial, nada de mais aqui. Retorna um int, e de fato retorn. Chama a função func, definida acima.

A linha obscura

A linha que guardamos para analisar contém uma operação de casting, atribuição e deferência, sendo o casting executado primeiro, operador unário que é, seguido pelo segundo operador unário, a deferência. Como sempre, a atribuição é uma das últimas. Descomprimida a expressão dessa linha, ficamos com algo parecido com as duas linhas abaixo:

int* p = (int*) 0;
*p = 0;

Não tem nada de errado em atribuir o valor 0 a um ponteiro, que é equivalente ao define NULL da biblioteca C (e C++). De acordo com a referência GNU, é recomendado o uso do define, mas nada impede utilizar o 0 “hardcoded”.

Porém, estamos escrevendo em um ponteiro nulo, o que com certeza é um comportamento não-definido de conseqüências provavelmente funestas. O ponteiro nulo é um ponteiro inválido que serve apenas para marcar um ponteiro como inválido. Se escrevermos em um endereço inválido, bem, não é preciso ler o padrão para saber o que vai acontecer =)

Atualização

Alguns amigos me avisaram sobre algo muito pertinente: dizer que acessar um ponteiro nulo, portanto inválido, é errado e nunca deve ser feito. Como um ponteiro nulo aponta para um endereço de memória inválido, acessá-lo irá gerar uma exceção no seu sistema operacional e fazer seu programa capotar. Um ponteiro nulo é uma maneira padrão e confiável de marcar o ponteiro como inválido, e testar isso facilmente através de um if. Mais uma vez: ponteiros nulos apontando para um endereço de memória inválido (o endereço 0) nunca devem ser acessados, apenas atribuído a ponteiros.

Em código. Isso pode:

int* p = 0; // atribuindo nulo a um ponteiro
int* p2 = p; // isso também pode

Isso não pode:

*p = 15; // NUNCA acessar ponteiros nulos
int x = *p; // isso também não pode, ler de um ponteiro nulo

Dito isso, me sinto melhor =)

Os diferentes erros na linguagem C

Uma coisa que me espanta de vez em quando é o total desconhecimento por programadores mais ou menos experientes dos níveis de erros que podem ocorrer em um fonte escrito em C ou C++. Desconheço o motivo, mas desconfio que o fato de outras linguagens não terem essa divisão de processos pode causar alguma nivelação entre as linguagens e fazer pensar que o processo de compilação em C é como em qualquer outra linguagem.

Porém, para começar, só de falarmos em compilação já estamos pegando apenas um pedaço do todo, que é a geração de um programa executável em C. Tradicionalmente, dividimos esse processo em três passos:

  1. Preprocessamento
  2. Compilação
  3. Linkedição

Vamos dar uma olhada mais de perto em cada um deles e descobrir erros típicos de cada processo.

Preprocessamento

O preprocessamento é especificado pelos padrões C e C++, mas, tecnicamente, não faz parte da linguagem. Ou seja, antes que qualquer regra de sintaxe seja verificada no código-fonte, o preprocessamento já terá terminado.

Essa parte do processo lida com substituição de texto e diretivas baseadas em arquivos e símbolos. Por exemplo, a diretiva de preprocessamento mais conhecida

#include <stdio.h>

faz com que todo o conteúdo do arquivo especificado seja incluído exatamente no ponto onde for colocada essa diretiva. Isso quer dizer que, antes sequer do código-fonte ser compilado, todo o conteúdo desse header padrão estará no corpo do arquivo C.

Para evitar que o mesmo header seja incluído inúmeras vezes dentro da mesma unidade em C, causando assim erros de redefinição, existe outra diretiva muito usada para cercar esses arquivos públicos:

#ifndef __MEUHEADER__ // se já estiver definido, caio fora até endif
#define __MEUHEADER__

// conteúdo do header

#endif // __MEUHEADER__

Esse conjunto de duas diretivas, por si só, é capaz de gerar os mais criativos e bizarros erros de compilação em C. E estamos falando de erros que ocorrem antes que sequer seja iniciado o processo de compilação propriamente dito. Obviamente que os erros serão capturados durante a compilação, mas o motivo deles terem ocorrido foi um erro decorrente do processo de preprocessamento. Por exemplo, vamos supor que um determinado fonte necessita de uma declaração de função contida em meuheader.h:

#include "header-do-mal.h"
#include "meuheader.h"

int func()
{
   meuheaderFunc();
}

Porém, num daqueles acasos da natureza, o header-do-mal.h define justamente o que não poderia definir jamais (obs.: e isso pode muito bem acontecer na vida real, se usamos definições muito comuns):

#ifndef __HEADERDOMAL__
#define __HEADERDOMAL__

 // tirei header da jogada, huahuahua (risos maléficos)
#define __MEUHEADER__

#endif // __HEADERDOMAL__

Na hora do preprocessamento, o preprocessador não irá mais incluir o conteúdo dentro de header.h:

#ifndef __MEUHEADER__ // se já estiver definido, caio fora até endif
#define __MEUHEADER__

int meuheaderFunc(); // talvez alguém precise disso

#endif // __MEUHEADER__

Conseqüentemente, durante a compilação do código-fonte já preprocessado, sem a declaração da função meuheaderFunc, irá ocorrer o seguinte erro:

error C3861: 'meuheaderFunc': identifier not found

Isso em fontes pequenos é facilmente identificável. Em fontes maiores, é preciso ter um pouco mais de cuidado.

Após o processo de preprocessamento, de todos os arquivos indicados terem sido incluídos, de todas as macros terem sido substituídas, todas as constantes colocadas literalmente no código-fonte, temos o que é chamado unidade de compilação, que será entregue ao compilador, que, por sua vez, irá começar a análise sintática de fato, descobrindo novos erros que podem ou não (como vimos) ter a ver com a fase anterior. A figura abaixo ilustra esse processo, com algumas trocas conhecidas:

Preprocessor

Dica: quando o bicho estiver pegando, e tudo o que você sabe sobre linguagem C não estiver te ajudando a resolver um problema, tente gerar uma unidade de compilação em C e analisar sua saída. Às vezes o que é claro no código pode se tornar obscuro após o preprocessamento. Para fazer isso no VC++ em linha de comando, use o parâmetro /E.

Compilação

Se você conseguir passar ileso para a fase de compilação, pode se considerar um mestre do preprocessamento. Por experiência própria, posso afirmar que a maior parte do tempo gasto corrigindo erros de compilação, por ironia do destino, não terá origem na compilação em si, mas no preprocessamento e linkedição. Isso porque o preprocessamento confunde muito o que vimos no nosso editor preferido, e a linkedição ocorre em uma fase onde não importa mais o que está dentro das funções, mas sim o escopo de nomes, um assunto um pouco mais vago do que a linguagem C.

Aqui você irá encontrar geralmente erros bem comportados, como conversão entre tipos, else sem if e esquecimento de pontuação ou parênteses.

int cannotConvertError(const char* message)
{
	int ret = message[0];
	return ret;
}

int ret = cannotConvertError(3);

error C2664: 'cannotConvertError' : cannot convert parameter 1 from 'int' to 'const char *'
if( test() )
	something;
	something-else;
else
	else-something;
error C2181: illegal else without matching if
while( (x < z) && func(x, func2(y) != 2 )
{
	something;
}
error C2143: syntax error : missing ')' before '{'

Claro, não estamos falando de erros relacionados a templates, que são um pesadelo à parte.

Dica: nunca subestime o poder de informação do compilador e da sua documentação. Se o erro tem um código (geralmente tem), procure a documentação sobre o código de erro específico, para ter uma idéia de por que esse erro costuma ocorrer, exemplos de código com esse erro e possíveis soluções. Ficar batendo a cabeça não vai ajudar em nada, e com o tempo, você irá ficar sabendo rapidamente o que aconteceu.

Linkedição

Chegando aqui, onde a esperança reside, tudo pode vir por água abaixo. Isso porque você já espera confiante que tudo dê certo, quando, na verdade, um erro bem colocado pode fazer você desistir pra sempre desse negócio de programar em C.

As características mais desejadas para corrigir erros nessa fase são:

  1. Total conhecimento da fase do preprocessamento
  2. Total conhecimento da fase da compilação
  3. Total conhecimento de níveis de escopo e assinatura de funções

Os dois primeiros itens são uma maldição previsível que deve-se carregar para todo o sempre. Se você não consegue entender o que aconteceu nas duas primeiras fases, dificilmente irá conseguir seguir adiante com essa empreitada. O terceiro item significa que deve-se levar em conta as bibliotecas que estamos usando, headers externos (com dependências externas), conflitos entre nomes, etc.

Alguns erros mais encontrados aqui são as funções não encontradas por falta da LIB certa ou por LIBs desatualizadas que não se encaixam mais com o projeto, fruto de muitas dores de cabeça de manutenção de código. Essa é a parte em que mais vale a pena saber organizar e definir uma interface clara entre os componentes de um projeto.

Do ponto de vista técnico, é a fase onde o linker junta todos os arquivos-objeto especificados, encontra as funções, métodos e classes necessárias e monta uma unidade executável, como ilustrado pela figura abaixo.

Linker

Dica: uma LIB, ou biblioteca, nada mais é que uma coleção de arquivos-objeto que já foram compilados, ou seja, já passaram pelas duas primeiras fases, mas ainda não foram linkeditados. Muitas vezes é importante manter compatibilidade entre LIBs e os projetos que as usam, de forma que o processo de linkedição ocorra da maneira menos dolorosa possível.

Erros além da imaginação

É óbvio que, por ter passado pelas três fases de transformação de um código-fonte em um programa executável, não quer dizer que este programa está livre de erros. Os famigerados erros de lógica podem se disfarçar até o último momento da compilação e só se mostrarem quando o código estiver rodando (de preferência, no cliente).

Entre esses erros, os mais comuns costumam se aproveitar de macros, como max, que usa mais de uma vez o parâmetro, que pode ser uma chamada com uma função. A função será chamada duas vezes, mesmo que aparentemente no código a chamada seja feita uma única vez:

#define max(a, b) ( a > b ? a : b )

int z = max( func(10), 30 );

Um outro erro que já encontrei algumas vezes é quando a definição de uma classe tem um sizeof diferente do compilado em sua LIB, pela exclusão ou adição de novos membros. Isso pode (vai) fazer com que, durante a execução, a pilha seja corrompida, membros diferentes sejam acessados, entre outras traquinagens. Esses erros costumam acusar a falta de sincronismo entre os headers usados e suas reais implementações.

Enfim, na vida real, é impossível catalogar todos os erros que podem ocorrer em um fonte em C. Se isso fosse possível, ou não existiriam bugs, ou pelo menos existiria uma ferramenta para automaticamente procurar por esses erros e corrigi-los.

Um projeto cheio de erros

Deixe uma resposta