Em funcionamento normal, os envelopes apenas redireccionam os pedidos de invocação, vindos do exterior do objecto composto, para a carta. Esta seria um modo de operação trivial se não pudessem ocorrer conversões de tipos nem existisse herança múltipla. O primeiro aspecto dificulta o processo de selecção de métodos. O segundo, além de ter as mesmas consequências que o primeiro, requer, no caso geral, a modificação de this em tempo de execução.
A utilização de herança múltipla, eventualmente associada a polimorfismo, constitui um aspecto crítico para o correcto funcionamento das invocações, obrigando à dedicação de especial atenção aos problemas que surgem com conversões de tipos e utilização indiscriminada dos vários tipos de polimorfismo disponíveis em C++, assim como das várias modalidades de herança. Esta, mesmo podendo não ser múltipla, pode apresentar dificuldades, e.g. herança virtual.
Se existir herança múltipla na hierarquia de cartas ela é replicada na
hierarquia de envelopes, tal como acontece no caso de herança simples.
Se existirem múltiplas raízes, cada uma delas derivará, virtualmente
da classe SPM_quot
LL_Envelope", tal como é ilustrado na
figura 6.2.
Figure 6.2: Ancoragem da hierarquia em LL_Envelope.
Quer no caso de herança simples, quer no de herança múltipla, todas as decisões de selecção de métodos a executar, passam a ser irrelevantes para a hierarquia de cartas, passando a ser da exclusiva responsabilidade da hierarquia de envelopes.
Cada envelope invoca, de forma directa, através de qualificação explícita, o método correspondente da carta. O exemplo abaixo mostra um método de um envelope, onde se pode ver como se processa uma invocação deste estilo. A invocação directa obriga que a carta esteja perfeitamente emparelhada com o respectivo envelope, isto é devem corresponder a classes correspondentes na hierarquias paralelas. A razão deve-se ao facto de o envelope, quando recebe uma invocação, realizar uma dupla conversão de tipos antes de invocar o método da carta. A conversão pode produzir dados erróneos se não se verificar a condição relativa ao emparelhamento.
// Código do envelope da classe Line... virtual void Insert(char c) { ((LineLetter *)((letter.typeid == __LineLetter__TypeName) ? ((LineLetter *)letter.letter) : (void *)0))->LineLetter::Insert(c); }
O código do método realmente gerado pela ferramenta que produz os
envelopes é uma chamada a uma macro, que se apresenta abaixo, e que
permite definir métodos arbitrários numa classe de envelopes. No caso
ilustrado acima, a chamada seria SPM_quot
LETENV_METHOD(Line, Insert, c)".
A macro esconde pormenores sobre a hierarquia de classes através
da chamada a SPM_quot
SPELLCAST". A sua expansão depende fortemente da
classe onde é utilizada.
#define LETENV_METHOD(type,method,args...) \ { \ if (!client.letter) SPELLCAST(type##Letter,letter)::method(args); \ else SPELLCAST(type##Client,client)::method(this , ##args); \ } #define SPELLCAST(type,obj) ((type*)DYN_CAST(type,obj))->type #define DYN_CAST(type,obj) type##_AUTO_CAST(obj.typeid,obj.letter)
A macro SPM_quot
DYN_CAST" efectua a conversão do objecto dentro de um
envelope para a sua classe real, i.e., aquela a partir da qual foi
criado. Note-se que neste caso a sua expansão resulta numa macro, que
se designará por MAC (macro automática de conversão), e cujo nome é
construído a partir de um dos argumentos, o tipo, e cuja definição é
automaticamente realizada por uma ferramenta que analisa a estrutura
de classes. No caso da classe SPM_quot
Line" ter-se-ia:
#define LineLetter_AUTO_CAST(ot,obj) ( \ _DYN_CAST(ot,LineLetter,obj) \ (void *)0) /* Error occured: object is of incorrect type */ #define _DYN_CAST(ot,st,obj) \ LL_TYPEID_CMP(ot,__##st##__TypeName) ? ((st *)obj) :
No caso de uma hierarquia múltipla, as MACs têm em conta a relação
entre classes e permitem apenas conversões válidas para as classes em
questão, i.e., para objectos da classe actual e das suas subclasses.
Existiria uma chamada a SPM_quot
_DYN_CAST" por cada uma das conversões
possíveis.
Resumindo: o modo genérico de invocar uma carta fica sujeito a duas conversões de tipos, que podem, em algumas circunstâncias ser inúteis. Em qualquer caso, o peso das conversões é pequeno quando comparado com a garantia de suporte à modificações de tipos que um ponteiro para um objecto pode sofrer.