Programação Orientada por Objectos jvo Aula 22 ( C++ VII ) Classes base virtuais Conversões implicitas na presença de classes base public Classes bases protected e private Classes base virtuais Voltemos ao exemplo da aula passada. Relembremos o diagrama de classes e a definição simplificada de cada uma das classes no diagrama. Pessoa class Pessoa { nome morada protected: string _nome_, _morada_; //... }; Estudante Trabalhador numero salario class Estudante: Public Pessoa{ /*…*/ }; class Trabalhador: public Trabalhador{ /*…*/ }; TrabalhadorEstudante class TrabalhadorEstudante: public Estudante, horas public Trabalhador{ /*...*/ }; Tal como foi definida, a classe TrabalhadorEstudante contém dois conjuntos de membros de dados de Pessoa: um conjunto (nome e morada) herdado através de Estudante e outro conjunto (nome e morada) herdado por Trabalhador. http://w3.ualg.pt/~jvo/poo/ POO 2002/2003 T22 - 1/8 Programação Orientada por Objectos jvo Para visualizarmos informalmente o que se passa em termos da memória necessária para guardar os membros de dados de um objecto TrabalhadorEstudante, podemos usar o seguinte diagrama. Neste diagrama, que nada tem que ver com UML, cada oval representa o espaço necessário para guardar os membros de dados da classe representada. Por exemplo, a oval relativa a Pessoa representa o espaço necessário para guardar o nome e a morada de Pessoa. As ligações representam composição. Por examplo, um objecto Estudante necessita de espaço para guardar os membros de dados que lhe são específicos (numero, neste caso) juntamente com os membros de dados de Pessoa. Pessoa Pessoa Estudante Trabalhador TrabalhadorEstudante O mecanismo da herança por defeito em C++, gera múltiplas instâncias das classes base. Como consequência, por exemplo, nome não pode ser utilizado directamente em TrabalhadorEstudante; teremos que utilizar o operador alcance para eliminar a ambiguidade e distinguir entre o nome relativa à parte de Estudante ( Estudante::nome ) e o nome da parte de Trabalhador ( Trabalhador::nome ). Assim TrabalhadorEstudante exibe ambiguidade desnecessária, uma vez que na realidade só necessitamos de guardar uma única vez os membros de dados de Pessoa. Podemos sobrecarregar o mecanismo de herança de defeito usando classes base virtuais. As classes base virtuais permitem ter em C++ um mecanismo de herança com um significado igual ao do nosso diagrama de classes em UML. Em termos de organização de memória isso corresponde a alterarmos o nosso diagrama informal da seguinte forma: http://w3.ualg.pt/~jvo/poo/ POO 2002/2003 T22 - 2/8 Programação Orientada por Objectos jvo Pessoa Estudante Trabalhador TrabalhadorEstudante Especificação de classes base virtuais Uma classe base é especificada como classe base virtual modificando a sua declaração com a palavra-chave virtual. class Estudante: public virtual Pessoa { /*...*/ }; // public virtual ou virtual public é igual. class Trabalhador: virtual public Pessoa { /*...*/ }; A definição ou declaração de classe Pessoa não precisa de ser alterada, para que seja tida como classe virtual. A declaração da classe TrabalhadorEstudante é, como antes: class TrabalhadorEstudante: public Estudante, public Trabalhador { /*...*/ }; Normalmente, uma classe derivada apenas pode inicializar explicitamente as suas classes base imediatas. As classes base virtuais são a excepção. Uma classe base virtual é inicializada pela sua classe “mais derivada”, isto é, é inicializada pela classe derivada na hierarquia que se encontra "mais longe" da classe base virtual. No nosso caso, Pessoa deverá ser inicializada por TrabalhadorEstudante. Isto é, o construtor deverá ter o seguinte aspecto: TrabalhadorEstudante ( /*...*/ ) : Pessoa ( /*...*/ ), Estudante ( /*...*/ ), Trabalhador ( /*...*/ ) { }; http://w3.ualg.pt/~jvo/poo/ POO 2002/2003 T22 - 3/8 Programação Orientada por Objectos jvo Se o construtor de TrabalhadorEstudante não inicializar explicitamente Pessoa, como no exemplo seguinte: TrabalhadorEstudante ( /*...*/ ) : Estudante ( /*...*/ ), Trabalhador ( /*...*/ ), { }; então será invocado o construtor de defeito de Pessoa, caso exista; será gerado um erro caso contrário. As inicializações de Pessoa por Estudante e por Trabalhador nunca são aplicadas à parte de Pessoa de TrabalhadorEstudante. As restantes classes da hierarquia serão inicializadas como habitualmente, independentemente de Pessoa ser, ou não, uma classe base virtual. Exemplo: Estudante :: Estudante ( /*...*/) : Pessoa ( /*...*/ ) { /*...*/ } Para resumir o que foi dito, apresentamos a nova versão da implementação da hierarquia de classes, em termos de declaração das classes e seus construtores, quando se usa classes base virtuais: class Pessoa { // como antes; s/ alteração; }; class Estudante: public virtual Pessoa { // ou : virtual public Pessoa { // ... public: Estudante ( const string& s, int i ): Pessoa (s) { _numero_= i }; // como antes; }; class Trabalhador: public virtual Pessoa { // ... public: Trabalhador ( const string& s, float f ): Pessoa (s){ _salario_= f ;} }; http://w3.ualg.pt/~jvo/poo/ POO 2002/2003 T22 - 4/8 Programação Orientada por Objectos jvo class TrabalhadorEstudante: public Estudante, public Trabalhador{ // como antes // ... public: TrabalhadorEstudante ( const string& s, int i, float f, int h ): Pessoa (s), Estudante ( s , i ), Trabalhador ( s , f ){ _horas_ = h; } //... }; Conversões implicitas na presença de classes base publicas Os membros herdados de uma classe base pública mantêm o seu nível de acesso dentro da classe derivada. Isto é, um membro público numa classe base pública, continuará a ser público numa classe derivada. Idem para membros protected e private. Aplicam-se quatro conversões implícitas pré-definidas entre uma classe derivada e a suas classes bases públicas. Veremos agora três delas e deixaremos a quarta para a próxima aula: 1. Um objecto de uma classe derivada será automaticamente convertido num objecto da sua classe base pública, em todo o sítio do programa onde se utilize um objecto dessa classe. 2. Uma referência para um objecto de uma classe derivada será automaticamente convertida numa referência para um objecto da sua classe base pública, em todo o sítio do programa onde se utilize uma referência dessa classe. 3. Um ponteiro para um objecto de uma classe derivada será automaticamente convertido num ponteiro para um objecto da sua classe base pública, em todo o sítio do programa onde se utilize um ponteiro dessa classe. Naturalmente que, um ponteiro para objecto de qualquer classe será implicitamente convertido num ponteiro do tipo void*. É seguro fazer a conversão de um objecto (referência, ou ponteiro) de uma classe derivada para um objecto (referência ou ponteiro) de uma classe base pública porque, como vimos antes, o objecto da classe derivada contém espaço para guardar informação http://w3.ualg.pt/~jvo/poo/ POO 2002/2003 T22 - 5/8 Programação Orientada por Objectos jvo relativa aos membros de dados da classe base pública e a visibilidade dos membros é mantida. Exemplo: TrabalhadorEstudante te(123, “Ana”, “Faro”, feminino, 100.0, 22); Estudante e=te; //ok: TrabalhadorEstudante é uma classe derivada // publica de Estudante => conversão implícita. Estudante *pe = &te; //ok Estudante &se = te; //ok Se se souber, como no exemplo acima, que um ponteiro ou referência de uma classe base referencia, de facto, um objecto de uma classe derivada, é possível fazer uma atribuição no sentido inverso, usando o operador static-cast <tipo>( ). Exemplo: TrabalhadorEstudante *pte=static-cast <TrabalhadorEstudante> pe; TrabalhadorEstudante &rpte=static-cast<TrabalhadorEstudante&> re; Classes base protected e private Como vimos, os membros herdados de uma classe base pública mantêm o seu nível de acesso dentro da classe derivada. Os membros public (e protected) herdados de uma classe base protected tornam-se membros protected da classe derivada. Os membros public, protected (e private) herdados de uma classe base private tornam-se membros private da classe derivada. Portanto, dentro de uma hierarquia, a interface pública herdada não está acessível ao programa cliente através de instâncias de uma classe com derivação não pública. Por consequência, não existem conversões implícitas de objectos (referências ou ponteiros) de classe derivada em objectos (referências ou ponteiros) da sua classe base não pública. http://w3.ualg.pt/~jvo/poo/ POO 2002/2003 T22 - 6/8 Programação Orientada por Objectos jvo Uma derivação pública define um relacionamento do tipo “é-um”, “é um tipo de”, ou “ é de espécie de” da classe base. Com a derivação não-pública deixamos de ter este tipo de relacionamento. Aplicações da herança privada Suponhamos que estávamos interessados em implementar um ADT stack com as suas operações push, pop e top. Suponhamos ainda que temos disponível a classe TypeList que possui todas as funções inerentes à manipulação de listas ligadas, com a declaração seguinte: typedef int Type; class TypeList { class Item { //... }; Item *list, *last; public: TypeList() {list=last=0;} TypeList(const TypeList&); void insert(const Type&); void append(const Type&); Type& first(void); int isEmpty(void); void removeFirst(void); //... }; Podemos implementar o stack recorrendo a herança privada, como se segue: class TypeStack : private TypeList { public: void push (const Type& t) { insert(t); }; http://w3.ualg.pt/~jvo/poo/ POO 2002/2003 T22 - 7/8 Programação Orientada por Objectos jvo Type pop() { Type tmp = first(); removeFirst(); return tmp; }; Type top() { return first(); } int isEmpty() { return TypeList::isEmpty();} // ou alternativamente: // TypeList::isEmpty; }; Note-se que isEmpty() teve que ser sobreposto ao membro isEmpty() original apenas porque a herança foi declarada privada. Note-se que usar herança pública seria o mesmo que afirmar que um stack é-uma lista; o que não é o caso. A abordagem seguida pode ser vista como uma alteração da interface da classe TypeList de forma a proporcionar as operações típicas de um stack. Deixa-se como exercício a implementação do stack reutilizando ainda a classe TypeList, mas sem recorrer a herança. Referências recomendadas para aprofundar os assuntos tratados: [1] Caleb Drake, Object-Oriented Programming with C++ and Smalltalk, Prentice Hall, Upper Saddle River, NJ, 1998. [2] S. Lippman, J. Lajoie, C++ Primer (3rd edition), Addison-Wesley, 1998. [3] B. Stroustrup, The C++ Programming Language (3rd edition) Addison-Wesley, 1997. http://w3.ualg.pt/~jvo/poo/ POO 2002/2003 T22 - 8/8