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
Download

Classes base virtuais