Para ajudar a comemorar o 25º aniversário do C++Builder, estou republicando um artigo muito antigo sobre as extensões da linguagem C++ introduzidas pela Borland na época.
Como você provavelmente já ouviu falar, logo após o 27º aniversário do Delphi, o C++ Builder está comemorando seu 25º aniversário. Você pode ler mais sobre a história do produto, neste excelente post do C++Builder PM David Millington em “ Celebrating 25 Years of C++Builder! ” Há outra postagem de blog muito interessante de David I sobre “ O 25º aniversário do C++Builder: Desenvolvimento visual, o poder da linguagem C++ e 2,5 décadas de excelência contínua “. Mesmo se você estiver mais interessado em Delphi, ambos os artigos ajudam a colocar dois dois produtos “gêmeos” na perspectiva correta.
Na época em que o Delphi foi lançado, eu estava focado principalmente em C++ (e em particular na biblioteca Borland C++ OWL para programação Windows) e tinha escrito 3 livros sobre a linguagem , o Borland IDE e a biblioteca. Enquanto eu mudei para o Delphi, o C++ ainda era uma parte ativa do meu foco quando o C++Builder surgiu. Dei uma sessão na SD West Conference sobre extensões de linguagem C++ Builder e escrevi bastante sobre esse tópico, mesmo que nunca tenha escrito um livro completo sobre isso, estando bastante ocupado com o lado Delphi.
Para comemorar o 25º aniversário do C++Builder, decidi publicar um dos meus artigos sobre a linguagem exatamente como ela. Isto é do material da conferência SD 97 West. Nem tudo ainda é preciso após 25 anos, mas a maior parte da descrição geral permanece válida. Mas em vez de editar para os idiomas de hoje, achei legal mantê-lo exatamente como foi escrito originalmente. Espero que você goste de lê-lo.
Table of Contents
Extensões de linguagem C++ Builder (ou: Programação Delphi em C++)
por Marco Cantu’
Quando o Delphi foi lançado, muitos programadores reclamaram: “Por que não é baseado na linguagem C++?”. Alguns deles (inclusive eu) responderam: “Simplesmente porque isso não é possível, devido à falta de muitos recursos da linguagem C++”. Agora que a Borland lançou o C++Builder, ou Delphi para C++, você pode pensar que vou começar a encontrar desculpas para minha declaração errada. Eu não estou! Ainda acho que minha afirmação está correta. Na verdade, o Borland C++ Builder não é baseado na linguagem ANSI C++, mas em uma versão fortemente estendida desta linguagem, que inclui quase todos os principais recursos da linguagem Object Pascal encontrada no Delphi.
Neste artigo vou discutir todas essas extensões de linguagem em detalhes, fornecendo algumas informações para programadores sem experiência com Delphi. Não vou cobrir todos os novos recursos do C++Builder, como as macros usadas para implementar parâmetros de matriz aberta ou o modelo usado para clonar conjuntos de Pascal. Vou me concentrar apenas nos recursos principais.
O que é um imóvel?
Um dos elementos-chave da programação visual é a ideia das propriedades de um objeto. Mas o que é uma propriedade? Uma propriedade é um nome com um tipo , relacionado a alguns dados ou funções de membro de acesso a dados de uma classe.
Uma das ideias básicas da OOP é que os membros de dados devem sempre ser privados. Então, muitas vezes você escreverá funções de membro público para obter e definir esse valor privado. Se você modificar a implementação posteriormente, poderá alterar facilmente o código dessas funções de acesso e evitar qualquer alteração no código que usa a classe.
Aqui está um exemplo básico (escrito seguindo as convenções de nomenclatura padrão para propriedades, campos e funções de acesso):
O código dessas duas funções de membro (não mostrado) simplesmente define ou retorna o valor do membro de dados privado fTotal .. Você pode usá-los da seguinte maneira:
[crayon-673a4f76f043d456235556/]Esta é a abordagem C++ padrão. No C++Builder podemos definir dentro de uma classe uma propriedade envolvendo estas funções de acesso:
[crayon-673a4f76f043f228517632/]Isso significa que agora podemos acessar esse valor de maneira mais uniforme, pois usamos a mesma notação tanto para ler quanto para escrever a propriedade:
[crayon-673a4f76f0440448260062/]Dependendo de seu papel em uma instrução, a expressão Form1->Total é traduzida pelo compilador em uma chamada para a função read ou write. Na verdade, o compilador pode traduzir uma expressão semelhante também em um acesso direto aos dados. Observe a seguinte declaração de propriedade:
[crayon-673a4f76f0441269656047/]Embora esse código pareça estranho a princípio, ele demonstra totalmente o papel das propriedades como um mecanismo de encapsulamento. De fato, podemos alterar posteriormente a declaração desta propriedade introduzindo uma de duas funções ao invés do acesso direto aos dados. Neste caso, precisaremos recompilar o código das classes que utilizam esta propriedade, mas não precisaremos modificá-lo.
Obviamente, as funções de acesso não estão restritas a ler e escrever dados privados, mas podem fazer qualquer coisa. Um dos efeitos típicos das funções de gravação é atualizar a interface do usuário. Aqui está uma nova versão da propriedade Value , seguida do código de sua função de escrita (escrito seguindo o estilo padrão):
[crayon-673a4f76f0443197243973/]Agora, se escrevermos as seguintes afirmações:
[crayon-673a4f76f0444214720966/]o efeito da alteração nos dados refletirá automaticamente na interface do usuário. Observe que isso também funciona com operadores. Ao aplicar o operador de incremento (++) à propriedade, seu valor é lido e o método Set é chamado. Eu realmente acho que as propriedades são um mecanismo de encapsulamento OOP de som!
Mais regras de idioma para propriedades
Além da estrutura básica que vimos, as declarações de propriedade permitem muitas alternativas. Uma ideia fundamental é que as propriedades têm um tipo de dados, mas são limitadas a um determinado conjunto de tipos de dados (incluindo a maioria dos tipos, strings e classes predefinidos).
As propriedades podem ser de leitura/gravação como nos exemplos acima, mas também podem ser somente leitura ou somente gravação. O que é comum é ver propriedades somente leitura, ou seja, valores que você pode ler, mas não alterar. Um exemplo óbvio de uma propriedade somente leitura na VCL (a Visual Component Library, hierarquia de classes do Delphi usada também pelo C++Builder) é a propriedade Handle de controles baseados em janela. Você pode pedir a um controle seu identificador do Windows, quando quiser chamar as funções da API do Windows diretamente, mas não deve alterar o identificador de uma janela. Para definir uma propriedade somente leitura, você simplesmente omite a declaração de escrita :
[crayon-673a4f76f0446088181853/]É possível declarar propriedades de array, ou seja, propriedades com um índice. Nesse caso, as funções de leitura e gravação necessárias têm um parâmetro extra, o próprio índice. Esse índice também deve ser usado para acessar os valores, pois não é possível acessar o array como um todo. A matriz pode não existir como um campo real da classe: quando você lê os itens de uma caixa de listagem, na verdade está solicitando ao Windows o valor do item (que não é duplicado dentro do objeto TListBox ). Também é possível declarar propriedades de array com vários índices (veja como exemplo a propriedade Pixels da classe TCanvas da VCL).
A visibilidade de acesso de uma propriedade
As propriedades podem ser declaradas usando qualquer um dos especificadores de acesso, incluindo private (embora isso faça pouco sentido), protected e public, além dos dois novos especificadores: __published e __automated. A propósito, propriedades somente leitura não podem ser publicadas.
Um campo ou método publicado não está disponível apenas em tempo de execução (como um elemento público), mas também em tempo de design. O compilador Borland C++Builder gera a identificação de tipo de tempo de execução (RTTI) no estilo Delphi para propriedades publicadas de classe derivada de TPersistent . Essas informações de tipo são usadas pelo ambiente de tempo de design, começando com o Object Inspector, mas também estão disponíveis para programadores que desejam se aprofundar na unidade TypInfo não documentada.
A palavra-chave publicada é geralmente usada para propriedades ou eventos, mas os formulários geralmente têm uma interface publicada, incluindo também subcomponentes e métodos de tratamento de eventos. Essas informações são analisadas automaticamente pelo ambiente de desenvolvimento e disponibilizadas no Object Inspector antes mesmo de você compilar o programa.
Por exemplo, enquanto a interface publicada de um componente é usada pelo Object Inspector para mostrar e editar valores de propriedade em tempo de design, a interface publicada de um formulário é usada pelo Object Inspector para encontrar componentes compatíveis com um determinado tipo de dados e funções de membro compatível com um determinado evento.
Há um quinto especificador de acesso, automatizado , que é usado para definir uma interface pública com informações de tipo de automação OLE correspondentes, possibilitando a criação de servidores de automação OLE. A palavra-chave __automated é usada nas subclasses TAutoObject .
Lembre-se também de que a visibilidade de uma propriedade pode ser estendida em classes derivadas. Uma propriedade protegida, por exemplo, pode ser declarada novamente como uma propriedade publicada. Para isso, não é necessário redefinir a propriedade, apenas declará-la novamente (a Borland usa o termo “propriedades içadas” para indicar esse comportamento). Ao declarar novamente uma propriedade, você também pode alterá-la, por exemplo, modificando seu valor padrão.
Fechamentos e Eventos
Quando as propriedades são do tipo de dados “ponteiro para uma função-membro” (também conhecida como closure ), elas são chamadas de eventos. Mas o que é um encerramento? Outra adição à linguagem C++ padrão. Um encerramento é uma espécie de ponteiro de função membro. Na verdade, ele associa um ponteiro a uma função-membro com um ponteiro para uma instância de classe, um objeto. O ponteiro para a instância da classe é usado como o ponteiro this ao chamar a função membro associada. Esta é a definição de um encerramento:
[crayon-673a4f76f0448464583640/]Como em C++ os ponteiros de função de membro também estão disponíveis, mas raramente são usados, você pode se perguntar para que precisamos dessas coisas estranhas? Fechamentos realmente importam no modelo Delphi. Na verdade, os eventos são encerramentos, mantendo o valor de uma função-membro do formulário que hospeda o componente relacionado. Por exemplo, um botão tem um encerramento, chamado OnClick , e você pode atribuir uma função de membro do formulário a ele. Quando um usuário clica no botão, essa função membro é executada, mesmo que você a tenha definido dentro de outra classe (normalmente, no formulário). Aqui está o que você pode escrever (e o C++ Builder geralmente escreve para você):
[crayon-673a4f76f044a873014760/]O código acima troca dois manipuladores de eventos em tempo de execução. Você também pode declarar explicitamente uma variável do tipo closure, como o tipo TNotifyEvent comum , e usá-la para trocar os manipuladores de dois eventos:
[crayon-673a4f76f044b780428368/]Isso significa que você pode atribuir uma função de membro (como BtnHelloClick ) a um encerramento ou evento tanto em tempo de design quanto em tempo de execução.
Propriedades e objetos de streaming
As classes do derivado de TPersistent possuem outras características importantes. Você pode salvar objetos dessas classes em um fluxo. A VCL não salva os dados internos de um objeto, mas simplesmente salva os valores de todas as suas propriedades (e eventos) publicadas. Quando o objeto é carregado, a VCL primeiro cria um novo objeto, depois reatribui cada uma de suas propriedades (e eventos). O exemplo mais óbvio de streaming de objetos é o uso de arquivos DFM. Esses arquivos binários armazenam as propriedades e os componentes de um formulário, e sugiro que você os carregue no editor C++Builder para estudar sua versão textual (você também pode usar o programa CONVERT.EXE de linha de comando para transformar um arquivo DFM em um arquivo TXT ou vice-versa)
O processo de streaming pode ser customizado de diferentes maneiras: adicionando uma cláusula padrão ou armazenada à declaração de propriedades ou substituindo o método DefineProperties . Quando você adiciona uma propriedade a um componente, geralmente adiciona à definição uma cláusula padrão . Se o valor de uma propriedade corresponder ao seu valor padrão, a propriedade não será salva em um fluxo junto com as outras propriedades de um objeto. O construtor da classe deve inicializar a propriedade com o mesmo valor padrão, ou essa técnica não funcionará. A diretiva armazenada , em vez disso, indica se uma propriedade deve ser salva no arquivo junto com o objeto ou não. O armazenado A diretiva pode ser seguida por um valor booleano ou uma função de membro retornando um resultado booleano (assim você pode escolher salvar o valor ou não dependendo do status atual do objeto). Por fim, o método DefineProperties permite criar pseudopropriedades, adicionando valores extras ao objeto transmitido e recarregando-os corretamente.
Mais RTTI ( dynamic_cast e mais)
Além das informações RTTI geradas pela palavra-chave __published , cada objeto possui vários métodos que você pode usar para consultar a si mesmo. Esses métodos fazem parte da classe TObject , a classe base de todos os objetos baseados em VCL. Você pode ver a lista dos métodos da classe TObject (incluindo funções membro estáticas) na Tabela 1.
Tabela 1: Funções de membro TObject
//funções de membro público
TObject
Livre
Tipo de classe
CleanupInstance
Endereço do campo
Após a construção
Antes da Destruição
Despacho
Manipulador padrão
FreeInstance
~Tobjeto
//função de membros públicos estáticos
InitInstance
Nome da classe
ClassNameIs
ClassParent
ClassInfo
InstanceSize
Herda de
MétodoEndereço
Nome do método
Para verificar se uma classe é de um determinado tipo, você pode usar o método ClassType . Isso é bastante comum com o parâmetro Sender de um manipulador de eventos. Este parâmetro refere-se ao objeto que gerou o evento, mas é do tipo genérico TObject . Ao associar o mesmo método a eventos de objetos diferentes, você pode querer verificar o tipo do objeto que causou o evento:
[crayon-673a4f76f044d083174685/]O __classid é outra palavra-chave adicionada ao C++Builder. Ele retorna a metaclasse do objeto (veja a próxima seção). A alternativa (nem sempre válida) é usar o parâmetro Sender de um manipulador de eventos e convertê-lo em um determinado tipo de dados, usando a técnica C++ dynamic_cast padrão. Isso faz sentido se conhecermos o tipo de dados ou de uma classe ancestral comum, como neste caso:
[crayon-673a4f76f0459137608271/]Como mencionei, além desses recursos de RTTI, o Delphi e o C++ Builder compartilham extensas informações de tipo, disponíveis em tempo de execução para cada objeto de uma classe derivada de TObject e com campos, propriedades ou métodos publicados. Por exemplo, você pode acessar uma propriedade dinamicamente, por nome; você pode obter a lista dos nomes das propriedades de uma classe; você pode obter a lista de parâmetros de um encerramento. Isso permite que os programadores construam ferramentas add-on muito poderosas para o ambiente, e é usado pelo próprio sistema como base das ferramentas de desenvolvimento visual, começando com o Object Inspector (na verdade, eu construí um clone de tempo de execução do Object Inspector) .
MetaClasses e Construtores Virtuais
Delphi introduziu o conceito de referência de classe, um ponteiro para as informações de tipo de uma classe. No C++ Builder, isso foi mapeado na ideia de uma classe TMetaclass, e o tipo TClass nada mais é do que um ponteiro para essa metaclasse. No Delphi o TClass tem um papel semelhante, mas uma definição diferente (embora as duas implementações sejam totalmente compatíveis).
Os métodos disponíveis para uma metaclasse correspondem exatamente aos métodos estáticos da classe TObject (na verdade, não existe uma classe TMetaclass no Delphi, mas uma referência de classe, que pode usar os métodos estáticos de TObject diretamente). Uma vez que você tenha uma variável TClass , você pode atribuir a ela uma classe, extraída do objeto (com a função ClassType ) ou obtida da própria classe usando a palavra-chave __classid . Aqui está um exemplo:
[crayon-673a4f76f045a255462693/]Você pode usar uma metaclasse quase como usa uma referência de classe no Delphi. O que não é possível no C++Builder é criar um novo objeto baseado em uma metaclasse. Isso é estranho: o C++Builder permite definir construtores virtuais, mas não fornece nenhuma maneira de chamá-los, a menos que você chame um método Delphi. Isso é o que acontece quando você define um novo componente no C++Builder e deixa a VCL lidar com ele (por exemplo, quando você carrega um componente de um fluxo, seu construtor virtual é chamado).
Para criar um objeto a partir de uma referência de classe, precisamos adicionar uma unidade Pascal simples ao nosso aplicativo C++Builder. Aqui está o código da função Pascal que podemos usar:
[crayon-673a4f76f045e428067360/]Podemos usar esta função em um aplicativo C++Builder. Este é o código simples que você pode usar para criar um componente de uma determinada classe em tempo de execução e colocá-lo dentro de um controle ScrollBox:
[crayon-673a4f76f0460395851651/]Este é o código que você pode usar para criar um componente do mesmo tipo do objeto Sender :
[crayon-673a4f76f0461015808326/]Conclusão
Como vimos, a Borland adicionou muitos novos recursos à linguagem C++ no C++ Builder para tornar essa linguagem compatível com Object Pascal e adaptada para programação baseada em componentes. Estender C++ dessa maneira pode parecer um pouco estranho, já que estamos tentando mapear as construções de uma linguagem para outra linguagem. No entanto, a facilidade de uso do ambiente e o desenvolvimento visual provavelmente compensam essa complexidade extra, que na maioria das vezes fica nos bastidores.
Ao mesmo tempo, o C++ Builder mantém total compatibilidade com o padrão ANSI C++, incluindo templates e o STL (para citar dois recursos não disponíveis para programadores Delphi), e permite misturar código baseado em VCL com código MFC ou OWL, embora isso torne sentido apenas se você tiver aplicativos existentes. A biblioteca VCL, na verdade, permite que você aproveite a programação visual e trabalhe com uma abstração mais alta do que as bibliotecas de classe Windows C++ típicas.
Existem muitas outras diferenças entre Delphi e C++Builder, mas a implementação de propriedades é o recurso de maior impacto em termos de extensões da linguagem C++. A maioria dos outros problemas (mapas de mensagens, conjuntos e parâmetros de matriz aberta, para citar apenas alguns) foram resolvidos usando palavras-chave C++ e recursos de linguagem existentes.
Marco Cantu’; é escritora e consultora freelancer, sediada na Itália. Ele escreveu livros de programação em C++ e Delphi, traduzidos em 10 idiomas em todo o mundo. Ele contribui para várias revistas, gosta de falar em conferências e ministra seminários avançados de Delphi e C++Builder em todo o mundo. Você pode contatá-lo em 100273.2610@compuserve.com ou http://ourworld. compuserve. com/homepages/marcocantu.
Escusado será dizer que as informações de contato da Compuserve e o site acima não são mais válidos, mas decidi deixar o artigo como foi publicado originalmente, incluindo minha biografia.