| Artigos | ||||||||||||||||||||
| Criando Componentes Reusáveis em C++ | ||||||||||||||||||||
| Do original: Creating Reusable C++ Components Lucent Technologies Palesta apresentada em C++ World Conference in Dallas, TX on Nov. 13, 1996. |
||||||||||||||||||||
| É um desafio criar componentes de
software que possam ser usados por outros desenvolvedores. C++ tem alguns dos mecanismos de encapsulamento que tornam possível criar componentes de fácil reutilização, mas a linguagem C++ impõe uma grande carga de detalhes ao trabalho dos desenvolvedores de classes. Esse tutorial apresenta alguns dos tópicos chave na criação de componentes de boa qualidade. |
||||||||||||||||||||
| Agenda | ||||||||||||||||||||
|
||||||||||||||||||||
| Notas: O tema principal desse tutorial é como você pode criar componentes reusáveis em C++ para as suas próprias áreas de aplicação. A criação de bons componentes não requer programação orientada a objeto, embora os princípios de análise e de projeto orientado a objeto sejam freqüentemente utilizados para decompor o domínio do problema, e para projetar a interface do programador. Muitas das idéias que são apresentadas nesse tutorial podem também ser usadas (com pequenas modificações) para produzir bons componentes reusáveis em C, Ada e Java |
||||||||||||||||||||
| Obstáculos a reutilização | ||||||||||||||||||||
Muitos costumam pensar que as suas dificuldades em
reutilizar componentes se devem a problemas ou deficiências técnicas:
Geralmente se pensa que se tivermos a tecnologia adequada a reutilização virá como conseqüência. A reutilização de software é a principal razão que leva muitos grupos de desenvolvimento de software a migrar para tecnologia orientada a objeto, embora a reutilização seja difícil de se atingir em um primeiro projeto orientado a objeto. |
||||||||||||||||||||
| Barreiras não-técnicas à reutilização | ||||||||||||||||||||
Quando falham várias tentativas de se implementar
reutilização de software dentro de uma organização, muitas pessoas se voltam para a
literatura sobre engenharia de software ou sobre reutilização de software, que apontam
muitas das barreiras não-técnicas, gerenciais e culturais que bloqueiam a reutilização
de software. Para superar esses problemas, nós tentamos
com o intuito de quebrar as barreiras e criar uma cultura de reutilização. Uma boa fonte de informação nessa área é o livro Confessions of a Used Program Salesman by Will Tracz (Addison-Wesley, 1995). |
||||||||||||||||||||
| De volta para às questões técnicas | ||||||||||||||||||||
É realmente necessário considerar as estratégias e os
problemas técnicos de reutilização de software, para que se tenha uma reutilização
efetiva. Em bibliotecas e em aplicações C++:
Há vários livros recentes que contêm boas orientações
para tornar seu código mais reutilizável: Vários dos principais pontos deste tutorial foram extraídos desses dois livros |
||||||||||||||||||||
| Quatro requerimentos de software reutilizável | ||||||||||||||||||||
Os fatores importantes em reutilização de software são
plenamente conhecidos há muito tempo. Esses fatores são:
Essa lista de requerimentos para reutilização foi extraída de Enhancing Reusability with Information Hiding, by David L. Parnas, Paul C. Clements, and David M. Weiss, ITT Proceedings of the Workshop on Reusability in Programming, 1983, pp. 240-247. Esse artigo foi reimpresso em um tutorial IEEE, de modo que pode ser encontradotambém em Peter Freeman, Tutorial: Software Reusability, IEEE Computer Society Press, 1988, pp. 91-95. |
||||||||||||||||||||
| Três tipos de componentware | ||||||||||||||||||||
Há várias e diferentes maneiras de se criar e usar
componentware:
Você deve usar a tecnologia de componentes adequada especificamente para sua aplicação. Isso significa que para alguns casos Visual Basic é ótimo, para outros casos, CORBA ou OLE/COM serão melhores, e ainda para outros, uma abordagem mais customizada usando classes C++ vai oferecer melhor flexibilidade e eficiência. |
||||||||||||||||||||
| Estratégias de reutilização em C++ | ||||||||||||||||||||
Algumas dicas para reutilização bem sucedida em C++:
A abordagem small is beaultifull é muito importante no projeto de classes C++. Criação de objetos que possam ser persistentes pode ajudar a ampliar a utilização de muitos componentes. |
||||||||||||||||||||
| Estratégia de pequenas classes | ||||||||||||||||||||
| Em muitos projetos de desenvolvimento em C++, há um
pequeno número de classes centrais, com interfaces
grandes e complexas. Um projeto desse tipo é uma garantia de que essas classes serão
específicas para uma única aplicação. É melhor a estratégia de criar uma grande número de classes individualmente mais simples, e potencialmente mais reutilizáveis. Classes muito grandes são geralmente o resultado de se reprojetar um sistema já existente, cujo projeto não foi orientado a objeto. Nesses casos você pode precisar ter algumas classes muito grandes para conter toda a funcionalidade do sistema existente. Isso é aceitável como ponto de partida, mas se você planeja continuar a fazer modificações no projeto ao longo do tempo, você tem que começar a decompor o antigo sistema em termos de grupos lógicos de dados. |
||||||||||||||||||||
| Classes pequenas para um problema em particular | ||||||||||||||||||||
Manter a interface para uma classe tão pequena quanto
possível é especialmente importante nos casos de classes que são definidas para o
âmbito de um problema em particular.
Você não deve considerar stardard class libraries como o melhor modelo a emular, quando projetar as interfaces se suas próprias classes para outros níveis de âmbito do problema. Reformulação é uma técnica para reorganizar uma hierarquia de classes mantendo interfaces similares para as classes derivadas na hierarquia. Em um caso muito simples, supondo que você queira extender uma classe concreta existente via herança. Você deveria, ao invés disso, escrever três classes: |
||||||||||||||||||||
| // before refactoring: a
concrete class // derived from another concrete class class Concrete_A { public: void f1(); virtual void f2(); }; class Concrete_B : public Concrete_A { public: void f2(); void f3(); }; // after refactoring: both concrete classes // are derived from an abstract class class Abstr_A { public: void f1(); // use old Concrete_A:: // f1() implementation virtual void f2() = 0; }; class Concrete_A : public Abstr_A { public: void f2(); } class Concrete_B : public Abstr_A { void f2(); void f3(); }; |
||||||||||||||||||||
| Mais a respeito de classes pequenas |
| Alguns iniciantes em C++ são relutantes em implementar
várias classes pequenas. É mais fácil induzi-los se você escrever um conjunto de
classes. Uma estratégia para reduzir o tamanho da interface da classe: criar uma classe de baixo-nível data-only mais várias classes de interface, que contêm as interfaces especializadas para um dos subsistemas da aplicação.
A reformulação se assemelha ao seguinte: |
| // before refactoring: one big class class BigClass { public: void f1(); int f2(); char *f3(); private: char bigbuf[512]; int file_descriptor; int current_state; }; // after refactoring: one low-level class // with multiple interface classes class BigClassRep { private: char bigbuf[512]; int file_descriptor; int current_state; friend class BigClass; friend class AltBigClass; }; class BigClass { public: // need constr and destr void f1(); int f2(); private: BigClassRep *data; }; class AltBigClass { public: // need constr and destr char *f3(); private: BigClassRep *data; }; |
| Representação em caracteres |
Um incentivo extra para a reutilização da classe em
múltiplos subsistemas é ter funções para representação do estado da classe em
caractéres, que possam ser usadas para armazenar o estado dos objetos em arquivos, ou
transmitir o estado dos objetos em uma mensagem.
A criação de funções operator>>() e operator<<() requer conhecimento sobre a biblioteca C++ iostreams. Uma boa referência é o livro C++ IOStreams Handbook by Steve Teale (Addison-Wesley, 1993). |
| Hierarquias de raiz única |
| Aguns desenvolvedores C++ optam por colocar todas as suas classes em uma hierarquia de raiz única: |
|
----------- | ZObject | ----------- | -------------- | LogObjects | -------------- / \ -------------- -------------- | CustAction | | RecordList | -------------- -------------- / \ | --------- ------------- ------------------ | Order | | Complaint | | CustRecordList | --------- ------------- ------------------ |
| Essa não é uma boa idéia. Embora essa solução torne
possível a existência de containers heterogêneos, facilita o uso equivocado de
containers. Esse é o caso de reutilização potencialmente perigosa. Esse tipo de hierarquia é a favorita dos antigos programadores Smalltalk, que se acostumaram a ter uma grande flexibilidade no tratamento de tipos de dados. Uma das bibliotecas de classes oringinais C++ (a NIH Class Library, escrita por Keith Gorlen at National Institutes of Health), tem todas as suas classes derivadas de uma base comum. |
| Mais a respeito de hierarquias de raiz única |
Hierarquia de herança única torna muito mais fácil
colocar as operações comuns no mais alto nível da hierarquia.
Isso é o que pensa normalmente o projetista de uma hisrarquia de raiz única: "Se isso é orientado a objeto, isso deve ser bom". É importante considerar o quanto a coleção de classes será portável, extensível e eficiente. |
| Mix-ins |
Um modo melhor para se ter polimorfismo flexível em uma
biblioteca é definir algumas classes base abstratas.
A estratégia mix-in é similar às interfaces em linguagem Java. Java normalmente permite apenas herança simples, mas também permite herança de classes de interfaces especiais que não têm implementação. Se você se restringe a esse tipo de herança múltipla nos projetos de bibliotecas C++, você vai evitar vários dos problemas de herança múltipla (por exemplo, se você não precisar de herança virtual) |
| A estratégia mix-in |
| Se você está escrevendo um função genérica (polimórfica) que deve operar com argumentos de diferentes tipos, você pode definir uma classe abstrata que contém exatamente a interface que função genérica necessita executar: |
| class Sortable { // abstract class public: virtual int compare(const Sortable &) const = 0; }; class ComplexNode : public Sortable { public: int compare(const Sortable &b) const { // return 0 if equal, negative if this is less than b, // positive if this is greater than b. } }; void sort(Sortable *first, Sortable *last) { .... use the virtual function compare() in here .... } |
| Embora seja fácil criar novos tipos mix-in,
é difícil aplicá-los a classes pré-existentes - você precisa criar uma classe
artificial extra, a qual herda de uma classe concreta e um mix-in. Por exemplo, se você deseja classificar strings, você deve criar um novo tipo de dado: |
| class SortableString : public string,
public Sortable { public: int compare(const Sortable &b) const { .... } }; |
| A alternativa template |
| Uma maneira simples de escrever funções genéricas é usar templates. |
| template <class T> sort(const T *first, const T *last) { .... you can still make calls to p->compare(*q) .... |
| O problema com funções genéricas usando templates: a
interface que uma função genérica requer é menos explícita (Você vai ainda obter
erros de compilação se os argumentos forem de tipos diferentes, ou se a classe dos
argumentos não definir uma função compare) A vantagem das funções genéricas usando templates: as funções genéricas podem ter um valor de argumento normal, além de argumentos pointers e referências. Essa abordagem com templates tem se tornado mais popular desde que a Standard Template Library tornou-se disponível. |
| Quão grande deve ser um arquivo fonte? |
| Isso depende. Quando codificam um conjunto de classes, alguns autores (por exemplo, John Lakos) acreditam na inclusão de tudo o que for possível em dois arquivos:
Outros autores (Carroll e Ellis) preconizam arquivos fonte menores:
Para bibliotecas de classe em desenvolvimento, o segundo esquema funciona um pouco melhor, na medida em que o código se torne mais maduro, é mais fácil ter-se tudo em um único arquivo. Geralmente é uma boa idéia, em projetos grandes, com em torno de um milhão de linhas de código, minimizar o número de arquivos fonte e arquivos-header. Deve-se considerar também o tempo consumido para link-editar uma aplicação muito grande. |
| Classes muito simples |
| As classes, consideradas individualmente, são mais
facilmente reutilizáveis se forem muito simples, se tiverem pequenas interfaces
públicas. Esse critério pode ser levado ao extremo: uma família de classes onde cada
uma tem apenas uma função membro pública. Essas classes atômicas podem maximizar a reutilização, mas também têm seus problemas:
Há algumas ferramentas e métodos comerciais baseados na criação de classes muito simples. Em particular, há uma ferramenta denominada ANSWER:Architect by Tony Martins at Claremont Technology. Na metodologia de Tony Martins há um pequeno número de categorias de classes simples, e há um processo sofisticado para conexão dessas classes em conjunto, formando uma rede de classes. |
| Estruturas |
| A última palavra em tecnologia de reutilização de
componentes em C++ é estruturas orientadas a objeto. Um exemplo:
Qual a principal diferença entre bibliotecas de componentes e estruturas?
Estruturas são bem mais difíceis de se escrever, e algumas vezes difíceis de se usar, mas promovem a reutilização de código. A reutilização em estruturas é mais reutilização de projeto do que reutilização de código. |
| Onde estruturas são úteis? |
Quando você deve preferir escrever uma estrutura, ao
invés de uma biblioteca de componentes?
Uma estrutura possui seja um principal, seja um conjunto de funções genéricas, que dependem de uma ou mais interfaces abstratas que são implementadas em diferentes classes concretas, escritas pelo programador usuário da estrutura. |
| Descobrindo as estruturas |
A criação de estruturas é normalmente um processo de
descoberta. É mais fácil transformar a implementação concreta de um grupo de classes
em uma estrutura extensível se:
Estruturas tornaram-se conhecidas em Smalltalk já há algum tempo, mas estruturas em C++ foram discutidas pela primeira vêz em um conjunto de artigos em 1988: Ralph E. Johnson and Brian Foote, Designing Reusable Classes, Journal of Object-Oriented Programming, June/July 1988, pp. 22-35. Andre Weinand, Erich Gamma, and Rudolf Marty, ET++ - An object-oriented application framework in C++, Proceedings of OOPSLA '88, pp. 46-57. |
| Testando classes C++ |
No projeto de conjuntos de classes e sistemas é
importante evitar as dependências recursivas ou recíprocas. Se você conseguir quebrar o
conjunto de classes em níveis, será mais fácil definir um plano de testes bottom-up
A técnica de dividir um sistema grande em níveis - denominada nivelização (levitalization) - é um dos conceitos de projeto físico mais importantes no livro de John Lakos, Large-Scale C++ Software Design. |
| Procurando oportunidades de reutilização |
| A maioria do esforço para reutilização é uma
combinação de varredura e novos investimentos Você nunca sabe, a priori, o que será bem sucedido:
Como mencionado por Andrew Loeing em uma de suas colunas JOOP: projeto de biblioteca é projeto de linguagem. A criação de um conjunto de classes é como definir uma extensão da linguagem para resolver um conjunto específico de problemas. |
| Comprando bons componentes |
Posto que o melhor tipo de reutilização é a reutilização de caixas-pretas, você não tem porque
requerer o código fonte dos componentes que você comprar.
Você precisa inspecionar a reusabilidade dos componentes:
Não há (ainda) um selo de qualidade para componentes C++, mas o comprador inteligente pode fazer uma série de verificações para assegurar-se de estar comprando componentes de boa qualidade. Um dos problemas mais comuns é a interação entre componentes ou diferentes versões de componentes. Por exemplo, se um componente para banco de dados persistente usa classes container de Rogue Wave, você pode precisar usar a mesma versão das bibliotecas Rogue Wave ao longo de toda a sua aplicação. |
| Oito dicas pra você implementar seus próprios componentes |
Essa é uma lista de dicas muito pessoais. Cada especialista em componentes terá sua própria lista, que será específica para a sua área de atuação. |
| Alocação de memória |
É importante ser um bom cidadão quando se trata de
memória dinâmica
Há dois modos de se evitar problemas com alocação de memória:
Purify é uma excelente ferramenta para localizar potenciais problemas de alocação de memória, e é de fácil utilização. Ainda não há substituto para análise e prevenção. |
| Alocação de memória dedicada |
Uma maneira de se obter melhor performance quando da
implementação de classes containers é fazer versões especiais para as funções new e delete para
nós do container
Operadores especializados new e delete são especialmente úteis em sistemas de tempo real, onde memória disponível é um prêmio. Você pode pré-alocar um pool de objetos de tamanho fixo no stack. |
| Ordem de inicialização estática |
| Se seu componente tem algumas classes internas de objetos
estáticos, que precisam ser inicializados antes de qualquer uso do componente, você
precisa controlar esse processo para assegurar que a inicialização ocorra como deve ser
(especialmente se alguém decide usar seu componente para inicializar um objeto estático
dentro da aplicação cliente). Há pelos menos três artifícios de linguagem para assegurar a seqüência correta de inicialização. O código nos exemplos seguintes visa garantir que certos objetos serão inicializados antes que sejam usados por outro código. |
| Um artifício de inicialização |
| Se você define uma classe que, para funcionar corretamente, requer a existência de um objeto estático inicializado, você precisa de uma classe auxiliar para fazer a inicialização: |
| /* file Myclass.h */ #ifndef __MYCLASS_H #define __MYCLASS_H class Myclass { private: static char *machname; // set to name of machine for lifetime // of the program public: static void set_machname(); static void unset_machname(); .... }; class Myclass_init { // initializer class (cf. Jerry Schwarz) private: static unsigned count; public: Myclass_init(); // the constructor will set the value of machname ~Myclass_init(); }; static Myclass_init Myclass_initobj; // 1 object per source file #endif |
| O artifício contido no exemplo acima é amplamente usado
(em código como a biblioteca iostreams de Jerry Schwarz). Nesse exemplo, a variável Myclass::machname deve ser inicializada o mais cedo possível, uma vez que os construtores de Myclass a utilizam. Já que o objeto estático |
| Myclass_init::Myclass_initobj |
| vai aparecer no arquivo fonte antes de qualquer objeto estático Myclass, o construtor Myclass_init será invocado primeiro. Tudo o que temos que fazer é assegurar que o construtor chame a função Myclass::set_machname() |
| Um artifício de inicialização (continuação) |
| Aqui estão as definições dos construtores e destrutores de Myclass_init |
| /* file Myclass.c */ #include "Myclass.h" #include <param.h> #include <sys/utsname.h> int Myclass_init::count = 0; Myclass_init::Myclass_init() { if (count++ == 0) Myclass::set_machname(); } Myclass_init::~Myclass_init() { if (--count == 0) Myclass:unset_machname(); } void Myclass::set_machname() { machname = new char[MAXHOSTNAMELEN + 1]; struct utsname u; uname(&u); strcpy(machname, u.nodename); } void Myclass::unset_machname() { delete [] machname; } |
| O objeto classe estática auxiliar que está definido no arquivo-header vai garantir que o construtor Myclass_init será chamado uma vêz para cada arquivo fonte que inclua Myclas.h.
O construtor fazer algo substancial apenas na primeira vêz em que for chamado, na segunda
vêz em que for chamado, e nas vezes subseqüentes, o construtor vai apenas somar 1 à
variável Myclass_init::count A definição do destrutor de Myclass_init vai também garantir que o código para limpeza seja executado exatamente uma vêz - quando o último arquivo, do conjunto de arquivos que incluem Myclass.h, tenha seu objeto estático destruído. |
| Um artifício de inicialização alternativo |
| Um outro modo de assegurar a correta utilização de um objeto estático é criar uma função que seja sempre utilizada para acessar o objeto: |
| /* file Myclass.h */ #ifndef __MYCLASS_H #define __MYCLASS_H #include <param.h> #include <sys/utsname.h> class Myclass { private: static char *machname_; // actual object public: static char *machname(); // accessor function static void set_machname(); // initialization function }; inline char *Myclass::machname() { if (machname_ == 0) Myclass::set_machname(); return machname_; } inline void Myclass::set_machname() { machname_ = new char[MAXHOSTNAMELEN + 1]; struct utsname u; uname(&u); strcpy(machname_, u.nodename); } #endif |
| Nesse exemplo, a variável machname_ é usada para
armazenar o valor real do pointer, e a função machname() é usada para acessar esse
valor Basicamente, esse mesmo método pode ser usado para inicializar variáveis que não sejam pointers, exceto que a função de acesso deve ser definida para retornar uma referência para o objeto. |
| /* file Myclass.h */ #ifndef __MYCLASS_H #define __MYCLASS_H #include <param.h> #include <sys/utsname.h> #include <String.h> class Myclass { private: static String *machname_; // pointer to actual object public: static String &machname(); // accessor function static void set_machname(); // initialization function }; inline String &Myclass::machname() { if (machname_ == 0) Myclass::set_machname(); return *machname_; } inline void Myclass::set_machname() { struct utsname u; uname(&u); machname_ = new String(u.nodename); } #endif |
| Um outro artifício de inicialização |
| O artifício anterior tem um desvantagem importante: nào destrói automaticamente o objeto ao término da execução. Uma maneira de superar esse problema é fazer um objeto function-static: |
| char *Myclass::machname() { static char lcl_machname[MAXHOSTNAMELEN + 1]; static int flag = 0; if (flag == 0) { flag = 1; struct utsname u; uname(&u); strcpy(lcl_machname, u.nodename); } return lcl_machname; } |
| Nesse caso, Myclass::set_machname() não pode ser
uma função inline. Se o objeto function-static
é um objeto de classe com destrutor, tal destrutor será executado quando do encerramento
do programa. Basicamente, esse mesmo método pode ser usado para inicializar variáveis que não sejam pointers, exceto que a função de acesso deve ser definida para retornar uma referência para o objeto. |
| String &Myclass::machname() { static String *lcl_machname = 0; if (lcl_machname == 0) { machname_ = lcl_machname; struct utsname u; uname(&u); lcl_machname = new String(u.nodename); } return *lcl_machname; } |
| Defina um único arquivo-header para sua interface |
| Uma boa estratégia é definir um único arquivo-header que contenha a definição da classe (ou as
definições) para seu componente, e também ter os comandos #include para
obter tudo o que o seu componente precisa para ser compilado corretamente Uma verificação simples, conforme sugerida por John Lakos:
|
| Não envie mensagens de erro |
| Essa não é uma regra absoluta. É aceitável desenvolver
componentes que contenham relatórios de seus próprio de erros, mas isso reduz a reusabilidade do
componente. Alternativas:
Mecanismos reservados para mensagens de erro do tipo UNIX (onde as mensagens são enviadas para stderr) não funcionam da mesma forma em outros ambientes, como MS Windows ou Macintosh. |
| Nunca exit |
Essa é uma outra recomendação, muitas vezes
desrespeitada.
Esse é um item de checklist para muitos programadores que procuram por componentes. Se um componente pode encerrar a execução do programa, é melhor não utilizá-lo. |
| Interface completa |
Muitas classes de componentes resultam de uma análise
orientada a objeto superficial, que define uma lista incompleta de comportamentos para os
métodos da classe.
Você não pode controlar totalmente a maneira como outros vão usar sua classe
Por isso é uma boa idéia inserir código que garanta que uma chamada imprópria não cause um estrago. |
| Componentes e estruturas padrões |
Há três estruturas especialmente importantes:
Você tem várias vantagens em estender estruturas existentes
|
| Standard Template Library |
| Já que a STL foi aceita pelo comitê ANSI C++ como parte
do padrão preliminar ANSI, é uma boa idéia construir futuros containers de modo
compatível com essa estrutura. A parte principal do trabalho é definir o iterator apropriado para sua coleção de classes:
A classe iterator é sempre uma classe aninhada dentro da sua classe container, e é derivado de um dos tipos básicos de iterator. Iterators são implementados normalmente da seguinte maneira: |
| #include <iterator.h> template <class T> class MyContainer { public: // implementation details of my container class // ..... class iterator : public bidirectional_iterator<T,difference_type> { private: // ... internal data structure for the iterator ... public: iterator() {} bool operator==(const iterator &) const; reference operator*() const; // returns a MyContainer& iterator& operator++(); // advance the iterator iterator operator++(int); // post-increment version iterator& operator--(); // move iterator back one object iterator operator--(int); // post-decrement version }; }; |
| Para maiores informações, veja STL Tutorial and Reference Guide by David R. Musser and Atul Saini (Addison-Wesley, 1996). |
| Classes iostreams |
| É complicado criar uma nova classe derivada a partir das
classes istream (input stream) e ostream (output stream). O trabalho mais difícil é criar uma nova classe especial que seja derivada de streambuf
As funções streambuf::underflow() e streambuf::underflow() são as mais importantes. O único trabalho necessário nas classes stream reais (as classes derivadas a partir de istream e ostream) é construir corretamente as classes streambuf derivadas e inicializar tudo corretamente. |
| Microsoft Foundation Classes |
| A estrutura MFC é similar a outras estrutruras GUI - a
classe document (derivada de CDocument) contém a
representação interna da da informação que será completa ou parcialmente apresentada
na tela. As classes view (derivadas de CView) mapeam a
representação interna para o objeto gráfico real (como caracteres, caixas, setas, etc)
que são efetivamente apresentadas na tela. Outras classes recebem os eventos do usuário (cliques no mouse, pressionamento de botões, etc) e atualizam tanto view quanto document. Classes MFC não funcionam muito bem com classes STL, porque os compiladores Microsoft C++ mais antigos tinham um suporte inadequado para templates. Isso deve ser melhorado aos poucos. Uma boa fonte de idéias sobre componentes baseados em MFC (fora da documentação da Microsoft) é Extending the MFC Library by David A. Schmitt (Addison-Wesley, 1996). |
| Resumo final |
Escrever componentes em C++ é compensador, mas é
trabalhoso. Há boas fontes de informação sobre
Com alguns desses recursos, seus esforços em reutilização de código serão melhor sucedidos |
| © 1996 DENNIS MANCL, Lucent Technologies - Bell Laboratories Tradução de Dagoberto Haele Arnaut |
| | Home | Bookmarks | Universidades | Para Saber mais | Universidades | WEB Directory | Mapa do site | | |