Renato Cardoso Mesquita
Departamento de Eng.
Elétrica da UFMG
[email protected]
Programação Orientada a Objetos
em C++
.
.
4. Programação Orientada
a Objetos em C++
.
.
.
.
.
.
Implementação dos mecanismos, conceitos e estruturas da Orientação a
Objetos, estudados no Capítulo 2;
• Abstração: Classes e suas instâncias (objetos). Abstração de dados
(atributos de um objeto ou de uma classe) e de comportamento
(funções membro (de objeto ou da classe)). Membros static contêm
atributos de classe ou métodos da classe; membros não static são
atributos e métodos dos objetos;
• Encapsulamento: Acessos aos membros das classes: public,
protected, private; declaração friend;
• Hierarquia: Conceito de Herança, itens 4.1 e 4.2.
• Modularidade: Programas em C++ podem ser implementados em
vários módulos; além disto, pode-se separar a interface (.h) da
implementação (.cpp). Em C++ tem-se também o conceito de
Namespaces que pode ser usado para auxiliar na modularização;
• Tipificação: um programa não deve aceitar intercâmbio com objetos
de tipos diferentes, a não ser onde isto seja efetivado de forma
controlada, através de construtores ou através de operadores de
conversão de tipos.
• Polimorfismo: Múltiplas formas, uma só interface: itens 4.3 e 4.4.
• Persistência: Objetos que mantêm seus atributos mesmo quando
acabam os seus escopos. C++ não suporta diretamente a persistência.
.
.
Programação Orientada a Objetos em C++ Pg. 2
4.1. Herança Simples
A partir de uma classe derivam-se novas classes, definindo-se uma
hierarquia, em que no primeiro nível está a classe base (ou superclasse,
ou classe pai) e, abaixo desta, estão suas derivações, chamadas classes
derivadas (ou subclasses, ou classes filhas)
Vantagens:
• Modelar relações entre classes: explicitar o que há de comum entre
algumas classes. Por exemplo, o que um triângulo e um círculo têm em
comum? à ambos são "tipos de formas geométricas"; o que um
funcionário e um cliente têm em comum? à ambos são "tipos de
pessoas" ...
• Aproveitar classes já existentes, implementando as modificações que
forem necessárias à especialização de classes, modificando-as e
mantendo intacto o código já existente.
• Muitas vezes, o código fonte da classe não está disponível (biblioteca
de classes) e sua alteração é impossível. Pode-se derivar novas classes,
realizando-se novas implementações aproveitando as características
desejáveis nas classes da biblioteca e definindo novos comportamentos
para o que queremos mudar.
Herança simples
• Na herança simples, toda classe possui apenas uma classe base. Na
herança múltipla, as classes derivadas possuem mais de uma classe
base.
Sintaxe:
class Nome_Derivada : [modificador_de_acesso] Nome_ Base {
...
// Declaração dos membros da classe derivada
};
class Animal {
protected:
int idade;
Programação Orientada a Objetos em C++ Pg. 3
public:
Animal(int id){idade=id;}
void mostra_idade();
void modifica_idade();
};
Class Ave : public Animal
{
protected:
char nome[20];
public:
Ave (int,char *);
mostra_nome();
};
Ave A(2, “Patativa”);
A.mostra_idade();
A.mostra_nome();
Animal
idade
mostra_idade()
modifica_idade()
Ave
nome
mostra_nome()
• Para que é usada a herança pública? Para modelar estruturas “É um
tipo de ...” : “Ave é um tipo de animal” possuindo seus atributos e
métodos, podendo substituir a classe base em situações onde se espera
receber um objeto da classe base. Se uma função espera um objeto do
tipo Animal como parâmetro, pode-se passar um objeto do tipo Ave!
• Um ponteiro para Animal pode apontar para um objeto Ave;
• Uma lista de Animais pode conter objetos Ave;
• etc ...
void func1(Animal &bicho); // Protótipo da função
...
Animal A;
Ave B;
...
func1(A) ; // OK, A é um Animal
func1(B); // OK, B é uma Ave, que é um tipo de Animal
void func2( Animal ani, Ave av)
{
Animal *ptani = &av; // OK, toda Ave "é um tipo de Animal"
Ave *ptav = &ani; // OOPS, erro: nem todo animal é uma
ave!
ptav->mostra_nome( ); // OOPS, erro: ptav estaria
apontando
Programação Orientada a Objetos em C++ Pg. 4
// para animal!
ptav = (Ave *) ptani; // Conversão na "marra". Funciona
// porque ptani aponta para uma Ave
ptav->mostra_nome( ); // Agora funciona ...
}
• Em síntese, um objeto da classe derivada pode ser acessado como se
fosse um objeto da classe base, quando for manipulado através de
ponteiros ou referências. O contrário não é válido!
• Classes obtidas por herança pública herdam todos os membros da
classe base. Porém, só têm acesso aos membros protegidos e públicos
da classe base, isto é, os membros privados da classe base não são
acessíveis na classe derivada!
Exemplo:
class Empregado {
private:
String primeiro_nome, ultimo_nome;
char inicial_do_nome_do_meio;
public:
void print( ) const;
String nome_completo( ) const ;
// ...
};
class Gerente : public Empregado {
private:
short nivel;
public:
void print( ) const;
};
void Gerente :: print( ) const
{
cout << " O nome é " << nome_completo( ) ; //OK!
// ...
}
void Gerente :: print( ) const
{
Programação Orientada a Objetos em C++ Pg. 5
cout << " O nome é " << primeiro_nome ; // ERRO!
// ...
}
O mais adequado seria a classe derivada acessar o que é público na classe
base, adicionando o que for específico dela. Assim:
void Gerente :: print( ) const
{
Empregado :: print( ) ; // As informacoes de Empregado
cout << nivel;
// E as informacoes especificas de Gerente
}
Classes derivadas não herdam os construtores, o operador de atribuição
("=") e o destrutor da classe base.
Construtores da classe derivada podem chamar um dos construtores da
classe base, através da seguinte sintaxe:
Nome_Classe Derivada :: Nome_Classe_Derivada (parâmetros da
classe base e da classe derivada) : Nome_Classe_Base
(parâmetros da classe base) {
...
// código do construtor da classe derivada
}
Exemplo 1:
Ave :: Ave(int id1, char * n1)
: Animal(id1)
{
strcpy (nome,n1);
}
Exemplo 2:
class Empregado {
private:
String primeiro_nome, ultimo_nome;
short departamento;
public:
Empregado( const String& n, int d);
Programação Orientada a Objetos em C++ Pg. 6
// ...
};
class Gerente : public Empregado {
private:
short nivel;
public:
Gerente( const string& n, int d, int nvl);
};
Gerente :: Gerente (const string& n, int d, int nvl)
: Empregado( n , d)
// Inicializa a base
{
nivel = nvl;
// Inicializa os membros
// ...
}
ou então:
Gerente :: Gerente (const string& n, int d, int nvl)
: Empregado( n , d),
// Inicializa a base
nivel(nvl)
// Inicializa os membros
{
// ...
}
Usando esta sintaxe, o construtor de Empregado poderia ser escrito como
Empregado :: Empregado( const String& n, int d)
: ultimo_nome(n), departamento(d) //Inicializa os membros
{
// ...
}
• Os objetos são construídos "de cima para baixo", isto é, primeiro é
construída a base, então os membros e depois é executado o código do
construtor da classe derivada.
• Quando não se faz uma chamada explícita ao construtor da classe
Base, o construtor sem parâmetros da classe base é chamado.
Nome_Classe Derivada :: Nome_Classe_Derivada (parâmetros da
classe derivada) {
Programação Orientada a Objetos em C++ Pg. 7
...
// código do construtor da classe derivada
}
• Quando a classe derivada não possui construtor, o construtor sem
parâmetos da classe base é chamado implicitamente em toda criação
de instância da derivada.
• Destrutores: Quando os objetos de classes derivadas deixam seu
escopo, ocorre a chamada de destrutores existentes ao longo da
hierarquia. A ordem de chamada dos destrutores é inversa à ordem de
chamada dos construtores à primeiro são chamados os destrutores
das classes derivadas, depois os membros da classe derivada são
destruídos, o destrutor da classe base é chamado e os membros da
classe base são destruídos.
Exemplo:
#include <string.h>
#include <iostream.h>
class Animal {
protected :
long quantidade;
public :
inline Animal (long qtde);
inline ~Animal ();
};
class Bovino : public Animal {
protected :
char raca[20];
public :
inline Bovino (char * ra, long qtde);
inline ~Bovino ();
};
class BovinoLeite : public Bovino {
protected:
long producao_diaria;
public :
BovinoLeite (long prod, char * ra, long qtde);
Programação Orientada a Objetos em C++ Pg. 8
~BovinoLeite ();
void mostrar ();
};
/************** Funcoes da Classe Animal**********/
Animal :: Animal (long qtde)
: quantidade(qtde)
{
cout << "\nEstou construindo um Animal";
}
Animal :: ~Animal()
{
cout << "Apagando um Animal ...\n";
}
/*************** Funcoes da Classe Bovino**************/
Bovino :: Bovino (char* ra, long qtde)
:Animal(qtde)
{
strcpy (raca,ra);
cout << "\nEstou construindo um Bovino";
}
Bovino :: ~Bovino()
{
cout << "Apagando um Bovino ...\n";
}
/********** Funcoes da Classe BovinoLeite ***********/
BovinoLeite :: BovinoLeite(long prod, char * ra, long qtde)
: Bovino (ra, qtde),
producao_diaria(prod)
{
cout << "\nEstou construindo um Bovino leiteiro";
}
BovinoLeite :: ~BovinoLeite()
{
cout << "Apagando um Bovino leiteiro ...\n";
}
Programação Orientada a Objetos em C++ Pg. 9
void BovinoLeite :: mostrar ()
{
cout << "\n\nQuantidade de Animais : " << quantidade;
cout << "\nRaca do Animal : "<< raca;
cout <<"\nProducao Diaria:"<< producao_diaria <<"\n\n";
}
int main ()
{
BovinoLeite vaca (10,"Holandesa",350);
vaca.mostrar();
return 0;
}
Resultados:
Estou construindo um Animal
Estou construindo um Bovino
Estou construindo um Bovino Leiteiro
Quantidade de animais: 350
Raca do animal: Holandesa
Producao Diaria: 10
Apagando um Bovino leiteiro ...
Apagando um Bovino ...
Apagando um Animal ...
OBS: O livro, na página 268 mostra a implementação de uma estrutura
Todo-Parte (“contem”) através de herança pública. Não se deve fazer
isto!!!! A herança pública é utilizada para implementar a estrutura “é
um tipo de” e não uma estrutura “contém”. Para se implementar a
estrutura Todo-Parte, devem ser utilizadas “camadas”!!!
Exemplo de uma classe construída por camadas de objetos:
class String { ... };
class Address { ... };
class PhoneNumber { ... };
class Person {
private:
String name;
Address address;
Programação Orientada a Objetos em C++ Pg. 10
PhoneNumber voiceNumber;
PhoneNumber faxNumber;
public:
...
};
Quando são usadas camadas (mesmo na herança pública, como já vimos),
deve-se fazer a inicialização dos objetos chamando-se os construtores na
inicialização e não no código dos construtores. Exemplo:
// Ineficiente! Não use!
Person :: Person(String &nome, Address &endereco,
PhoneNumber &telefone, PhoneNumber &fax)
{
name = nome;
address = endereco;
voiceNumber = telefone;
faxNumber = fax;
// Restante do Código do Construtor
}
// Maneira eficiente de fazer a inicialização
Person :: Person(String &nome, Address &endereco,
PhoneNumber &telefone, PhoneNumber &fax)
: name(nome),
address(endereco),
voiceNumber(telefone),
faxNumber(fax)
{
// Restante do Código do Construtor
}
Porque o segundo construtor é mais eficiente? Porque a construção de
objetos ocorre em duas etapas:
1. É feita a inicialização dos atributos, na ordem da declaração destes na
classe;
2. Execução do corpo do construtor que foi chamado.
à Um construtor para String e Address, além de 2 para PhoneNumber
serão chamados, antes mesmo de se entrar no corpo do construtor de
Person!!! à Usar a segunda forma é mais eficiente!
Programação Orientada a Objetos em C++ Pg. 11
Operador de atribuição: Deve-se lembrar de fazer a atribuição para
todos os atributos de uma classe na sobrecarga do operador de atribuição.
class A
{
private:
int x;
public:
A(int val_inicial) : x(val_inicial) { }
A& operator = (A &);
};
A& A:: operator = (A & atrib)
{
if (this == &atrib) return *this;
x = atrib.x;
return *this;
}
class B : public A
{
private:
int y;
public:
B(int val_inicial) : A(val_inicial), y(val_inicial) { }
B& operator = (B &);
};
// Operador de atribuição errado para a classe B
B& B:: operator= (B & atrib)
{
if (this == &atrib) return *this;
y = atrib.y; // e x ?????
return *this;
}
// Operador de atribuição corrigido!!
B& B:: operator= (B & atrib)
{
if (this == &atrib) return *this;
A::operator=(atrib); // Chamada ao operador = de A
Programação Orientada a Objetos em C++ Pg. 12
y = atrib.y;
return *this;
}
// Outra maneira de corrigir a atribuição!!
B& B:: operator = (B & atrib)
{
if (this == &atrib) return *this;
((A&) *this) = atrib; //Atribuição à parte A do objeto
y = atrib.y;
return *this;
}
Membros protegidos:
• O modelo de membros privados/públicos funciona bem em classes
que implementam tipos concretos. Porém, quando uma hierarquia de
classes é introduzida, existe a questão: que acesso aos membros
fornecer às classes derivadas?
• Membros privados de uma classe não são acessíveis por ninguém, a
não ser pelos membros da própria classe;
• Membros públicos de uma classe são acessíveis por todo mundo;
• Membros protegidos de uma classe não são acessíveis pelo "público
em geral" mas são acessíveis pelos membros da classe derivada e seus
friends.
è O objetivo de se declarar membros protegidos em uma classe base é
torná-los acessíveis às funções membro das classes derivadas.
Pode-se, também, especificar um modificador de acesso na herança, que
pode ser privado, protegido ou público:
class X : public B { /* .... */};
class Y : protected B { /* .... */};
class Z : private B { /* ..... */};
Programação Orientada a Objetos em C++ Pg. 13
Acesso aos membros da classe base que se terá na classe derivada
Tipo de acesso na
classe Base
public
private
protected
public
private
protected
public
private
protected
Modificador de
Acesso da Herança
public
public
public
protected
protected
protected
private
private
private
Tipo de Acesso na
Classe Derivada
public
inacessível
protected
protected
inacessível
protected
private
inacessível
private
Modificador de acesso da Herança Default : Private!
class Z : B { /* ..... */};
4.2. Herança múltipla
• A herança múltipla ocorre quando uma classe derivada possui duas ou
mais classes base imediatas, ou seja é filha de mais de uma classe;
• Obtém-se uma classe “híbrida” que combina características de várias
classes;
Sintaxe:
class Classe_derivada : [modificador] Classe_base1,
[modificador] Classe_base2, ..., [modificador] Classe_basen {
// declaração dos membros da classe
};
Exemplo:
class Hibrida : public A, private B, public C {
private:
....
public:
....
};
Programação Orientada a Objetos em C++ Pg. 14
Exemplo: A classe derivada deriva os atributos e pode usar os
métodos das classes base.
class Task {
// ...
public:
void delay(int);
// ...
};
class Displayed{
// ...
public:
void draw( );
};
class Satellite : public Task, public Displayed {
//...
public:
void transmit( );
};
void f( Sattelite& s)
{
s.draw( );
s.delay(10);
s.transmit( );
}
// Displayed::draw( )
// Task::delay( )
// Satellite::transmit()
• Note que com herança simples as escolhas do programador para
implementar as classes seriam limitadas. Satellite poderia ser um tipo
de Task ou um tipo de Displayed, mas nunca de ambas as classes
simultaneamente.
• A classe derivada pode ser passada para funções que esperam receber
uma das classes base: Satellite é um tipo de Task, mas também é um
tipo de Displayed!
void highlight(Displayed*);
void suspend (Task*);
void g(Satellite* p)
{
Programação Orientada a Objetos em C++ Pg. 15
highlight(p); // passa um ponteiro para a "porção Displayed" de p
suspend(p); // passa um ponteiro para a "porção Task" de p
}
• Os construtores da classe derivada podem chamar construtores das
classes base. Para tal, os parâmetros dos construtores da classe
derivada devem ser os que ele mesmo usa e os que deseja passar aos
construtores das bases:
Classe_derivada :: Classe_derivada (parâmetros da
Classe_derivada, parâmetros da Classe_base1, parâmetros da
Classe_base2, ..., parâmetros da Classe_basen)
: Classe_base1(parâmetros da Classe_base1),
Classe_base2(parâmetros da Classe_base2),
...
Classe_basen(parâmetros da Classe_basen)
{
// código do construtor da Classe_derivada
}
Exemplo (para código completo ver página 271):
class Horizontal {
char tipoh;
protected :
int posicao_horizontal(char *);
public :
Horizontal(char tipo) {tipoh = tipo;}
};
class Vertical {
char tipov;
protected :
int posicao_vertical();
public :
Vertical (char tipo) {tipov = tipo;};
};
class Mensagem : public Horizontal, public Vertical {
char * msg;
public :
Mensagem (char * texto, char ver, char hor);
Programação Orientada a Objetos em C++ Pg. 16
~Mensagem();
};
/* Construtor de Mensagem */
Mensagem :: Mensagem (char * texto, char ver, char hor)
: Vertical(ver), Horizontal(hor)
{
msg = new char[strlen(texto)+1];
if (!msg)
{
cout << " Memoria Insuficiente";
return;
}
strcpy(msg, texto);
}
Mensagem :: ~Mensagem()
{
delete [ ] msg;
}
Ambiguidades em herança múltipla:
1- Classes Base possuem membros homônimos.
• Quando o objeto da classe derivada tentar referenciar o membro
homônimo, o compilador não saberá a qual das classes se está
referindo.
class Loteria {
public:
void draw();
...
};
class Objeto_Grafico {
public:
void draw():
...
};
class SimulaLoteria : public Loteria, public Objeto_Grafico {
Programação Orientada a Objetos em C++ Pg. 17
// Não redeclara draw() !!
...
};
SimulaLoteria simul;
simul.draw(); // Erro! Ambiguidade!
simul.Loteria::draw(); // OK!
simul.Objeto_Grafico::draw(); //OK!
• No exemplo anterior as funções têm o mesmo protótipo nas classes
base. E se tivessem protótipos diferentes, a sobrecarga de funções não
resolveria o problema? NÃO!!!
class Task {
// ...
public:
void debug (double p);
};
class Displayed {
// ...
public:
void debug(int v);
};
class Satellite : public Task, public Displayed {
// ...
};
void g (Satellite* p)
{
p->debug(1);
p->Task::debug(1);
p->Displayed::debug(1);
}
// Erro de ambiguidade ...
// OK
//OK
E se a escolha dos nomes nas diferentes classes base tivesse sido uma
decisão deliberada e se quisesse a seleção da função a ser utilizada
baseando-se nos tipos de argumentos? Teríamos que usar a declaração
using, trazendo as funções para um escopo comum: o da classe derivada.
Programação Orientada a Objetos em C++ Pg. 18
class A {
public:
int f(int);
char f(char);
// ...
};
class B {
public:
double f(double);
// ...
};
class AB : public A, public B {
public:
using A::f;
using B::f;
char f(char); // esconde A::f(char)
AB f(AB);
};
void g (AB& ab)
{
ab.f(1);
ab.f('a');
ab.f(2.0);
ab.f(ab);
}
// A::f(int)
// AB::f(char)
// B::f(double)
// AB::f(AB)
2- Duplicidade de atributo, através de classes base replicadas.
class ClasA {
protected :
char nome[31];
public :
void mostrar () {cout << "\nNome na Classe\t: " << nome;}
};
class ClasB1 : public ClasA {
public :
ClasB1 (char * n = "Classe B1") {strcpy(nome,n);}
void mostrar() {ClasA::mostrar();};
};
Programação Orientada a Objetos em C++ Pg. 19
class ClasB2 : public ClasA {
public :
ClasB2(char * n = "Classe B2") {strcpy(nome,n);};
void mostrar() {ClasA::mostrar();};
};
class ClasC : public ClasB1, public ClasB2 {
public :
void saida () {
ClasB1::mostrar();
ClasB2::mostrar();
strcpy(nome,"ObjClasseC"); // OOPS!! Ambiguidade
};
A
A
B1
B2
C
int main () {
ClasC objClasC;
objClasC.saida();
return 0;
}
• A declaração do objeto da classe ClasC aciona o construtor "default"
da "ClasC()", criado pelo compilador e sem código, este chama os
construtores das classes base de "ClasC", na ordem declarada na
herança, usando os valores "default".
• ClasB1::mostrar() e ClasB2::mostrar(), MOSTRAM RESULTADOS
DIFERENTES à Existem dois atributos nome em ClasC! Isto não é
necessariamente um erro e pode até ser o efeito esperado!
• Se não se deseja ter dois significados para os atributos da classe
base quando são acessados pelas diferentes bases da herança
múltipla, deve-se usar a declaração "virtual" nas derivações da raiz.
A
class ClasB1 : public virtual ClasA {
...
};
class ClasB2 : public virtual ClasA {
...
};
class ClasC : public ClasB1, public ClasB2 {
public :
B1
B2
C
Programação Orientada a Objetos em C++ Pg. 20
void saida () {
ClasB1::mostrar(); // Apresenta Classe B2
ClasB2::mostrar(); // Apresenta Classe B2
strcpy(nome,"ObjClasseC"); // Agora está OK!
};
• Restrição no uso de classes virtuais: a inicialização dos atributos em
uma classe base virtual deve ser feita em um construtor default pelas
classes que declaram a derivação virtual, pois o contrutor vai ser
chamado uma única vez e diretamente pelo construtor da classe mais
em baixo na hierarquia. Isto significa que a classe base virtual deve ter
um construtor default( ou com default para todos os seus parâmetros)
• Se modificarmos a classe A do exemplo anterior para:
class ClasA {
protected :
char nome[31];
public :
ClasA(char *s) { strcpy(nome,s);}
void mostrar () {cout << "\nNome na Classe\t: " << nome;}
};
O programa apresentará três erros, um para cada construtor da hierarquia!
Portanto, deveríamos ter o construtor modificado para:
ClasA(char *s =" ") { strcpy(nome,s);}
4. 3. Polimorfismo e funções virtuais
4.3.1. Problemas com "tipos concretos":
• Um tipo concreto, como Date, String, define uma espécie de "caixa
preta". Uma vez que a caixa preta é definida, ela não possui mais
alguma forma de se adaptar a novos usos, exceto pela modificação de
sua definição. Esta situação pode ser a desejada, mas ela também pode
levar a uma certa inflexibilidade.
• Considere a definição de um tipo como "Shape", a ser usado em um
sistema gráfico. Assuma que o sistema deva suportar círculos,
triângulos e quadrados, e que tenhamos as seguintes classes:
class Point { /* ... */};
Programação Orientada a Objetos em C++ Pg. 21
class Color{ /* ... */};
enum Kind { circle, triangle, square };
class Shape {
Kind k;
//Tipo
Point center;
Color col;
// ...
public:
void draw( );
void rotate(float ang);
// ...
};
No caso desta classe, o membro Kind é necessário para especificar qual é
o tipo de Shape que realmente se está armazenando, de modo que
operações como draw() e rotate() possam ser executadas de modo
correto. A função draw() seria semelhante a:
void Shape :: draw( )
{
switch (k) {
case circle:
// desenha um circulo
break;
case triangle:
// desenha um triangulo
break;
case square:
// desenha um quadrado
break;
}
}
Quais os problemas com esta estratégia?
• Funções como draw() precisam conhecer todos os tipos de Shape que
possam existir;
• A função deve ser modificada toda vez que uma nova forma for
adicionada ao sistema à o código de draw é aumentado a cada
inclusão de nova forma;
Programação Orientada a Objetos em C++ Pg. 22
• Na realidade, todas as funções que trabalham com shapes terão que
ser examinadas e modificadas a cada nova forma adicionada;
• Só poderemos adicionar uma nova forma, se tivermos acesso ao
código fonte de todas as operações;
• A parte de dados de Shape tem que ser escolhida de maneira que
acomode todas as possíveis representações de uma forma, seja ela
qual for ...
Qual a saída?
• A saída está em se agrupar o que é comum a todas as Shapes (isto é,
elas possuem uma forma, uma cor, podem ser desenhadas, giradas, etc
..) em uma classe, e as propriedades específicas das diversas Shapes
em classes derivadas ... OK, isto a gente já sabe fazer: herança!
• Mas e se quisermos mais ... Especificar qual deve ser a interface de
Shape, fazer com que todas as classes derivadas compartilhem esta
interface e que possamos declarar um container de ponteiros para
Shape e que possamos escrever funções genéricas que manipulem este
container de shapes, sem saber qual shape específica está armazenada
em cada região de memória apontada por cada ponteiro do container?
à vamos precisar do polimorfismo ...
Shape
Shape * forma [3];
forma[0] = new Triangle;
virtual void draw();
forma[1] = new Square;
forma[2] = new Circle;
Triangle
Square
Circle
for(int i=0; i < 3; i++)
forma[i]->draw();
void draw();
void draw();
void draw();
4.3.2. Polimorfismo:
• Implica funções homônimas para lógicas semelhantes ao longo de uma
hierarquia;
• Para que haja polimorfismo, a ligação entre o objeto e a função deve
ser feita em tempo de execução (ligação dinâmica). Existe também a
possibilidade de utilizarmos funções com o mesmo nome com ligação
entre o objeto e a função feita em tempo de compilação: neste caso,
não temos polimorfismo e sim sobrecarga de função (ligação estática)!
Programação Orientada a Objetos em C++ Pg. 23
• A implementação do polimorfismo em C++ pressupõe a utilização de
funções virtuais. Quando uma classe base tem funções membro que
devem ter comportamentos diferentes para suas derivadas, deve-se
declará-las como virtuais na classe base e redefinir seu código nas
derivadas.
class ClasseBase{
...
virtual protótipo_função_virtual(); ...
};
class ClasseDerivada : ClasseBase {
...
protótipo_função_virtual(); ...
};
• Opcionalmente, pode-se repetir a palavra reservada virtual na
declaração da função na classe derivada;
• Sempre que o código original da classe base não implementa a lógica
que desejamos na classe derivada, basta implementar uma nova lógica
em outro código para a função na classe derivada.
Exemplo:
Sistema para gerenciamento de Transações Comerciais com clientes de
uma loja: Classe Base: Transação, derivando Venda e desta
Venda_Prazo.
#include <iostream.h>
#include <string.h>
class Transacao {
char cliente[31];
public :
Transacao(char * nome) {strcpy(cliente,nome);};
virtual void mostrar();
};
class Venda : public Transacao {
protected :
double valor;
public :
Programação Orientada a Objetos em C++ Pg. 24
Venda (char *, double);
void mostrar();
};
class VendaPrazo : public Venda {
float juros;
public :
VendaPrazo(char *, double, float);
void mostrar();
};
int main () {
char nome[31];
double preco; float taxa;
Transacao * vendas[3];
cout << " Nome : " ;
cin.getline(nome,30);
cout << " Valor a Vista : ";
cin >> preco;
cout << " Taxa de Juros (%) : ";
cin >> taxa;
vendas[0] = new Transacao(nome);
vendas[1] = new Venda (nome,preco);
vendas[2] = new VendaPrazo(nome,preco,taxa);
for (int i = 0; i < 3; i++)
{
cout << i+1 << " "; vendas[i]->mostrar();
delete vendas[i];
}
return 0;
}
void Transacao :: mostrar()
{
cout << "Nome do Cliente : " << cliente << '\n';
}
Venda ::Venda (char* nome, double preco)
:Transacao(nome)
{
valor = preco;
Programação Orientada a Objetos em C++ Pg. 25
}
void Venda :: mostrar() {
Transacao::mostrar();
cout << " Valor da Compra : " << valor << '\n';
}
VendaPrazo :: VendaPrazo (char * nome, double preco, float taxa)
: Venda (nome, preco)
{
juros = taxa;
}
void VendaPrazo :: mostrar() {
Transacao::mostrar();
cout << " Taxa Praticada : " << juros << '\n'
<< " Valor da Compra : " << valor*(100+juros)/100 << '\n';
}
• A ligação objeto-função é efetivada em tempo de execução. Para tal,
e' necessário o emprego de função virtual.
• Se for removida a declaração "virtual" na definição da classe
"Transação", os resultados mostrarão apenas o nome da pessoa, nas
três impressões. A declaração "virtual" adia a ligação para a
execução do programa.
• Pode-se também usar referências para efetuar a ligação em tempo de
execução:
void func1(Transacao &obj)
{
obj.mostrar();
}
Transacao tr(nome);
Venda vv(nome,preco);
VendaPrazo vp(nome,preco,taxa);
func1(tr); // Chama Transacao::mostrar()
func1(vv); // Chama Venda::mostrar()
func1(vp); // Chama VendaPrazo::mostrar()
• A função deve ser declarada como virtual na classe Base;
• A função virtual deve estar definida para a classe Base;
Programação Orientada a Objetos em C++ Pg. 26
• Os protótipos das funções nas classes derivadas devem ser idênticos
aos protótipos na classe base à protótipos diferentes são considerados
sobrecarga de função!!!
• Funções virtuais não podem ser estáticas (pois funções estáticas não
possuem o ponteiro this!);
• Nunca é demais repetir: para se ter comportamento polimórfico é
necessário que a função na classe base seja virtual e que estejamos
manipulando objetos através de ponteiros ou referências para a classe
base. Se não for este o caso, o compilador executa a ligação entre o
objeto e a função a ser chamada em tempo de compilação!
• Construtores não podem ser funções virtuais;
• Destrutores podem ser virtuais: na realidade, se vamos usar
polimorfismo, os destrutores DEVEM ser virtuais.
Exemplo de problemas causados por destrutores não virtuais:
//Código com problema: destrutor de Base não virtual
class Base {
public :
~Base() {cout << "\n\tDestruindo a Parte da Base!";}
};
class Derivada : public Base {
public :
~Derivada() {cout << "\n\tDestruindo a Parte da Derivada!";}
};
int main()
{
Base base;
Derivada deriv;
base = deriv;
Base * ptrBase = new Base;
cout << "\nDeleta ObjBase : ";
delete ptrBase;
ptrBase = new Derivada;
cout << "\nDeleta ObjDeriv : ";
delete ptrBase;
return 0;
}
Resultados:
Programação Orientada a Objetos em C++ Pg. 27
Deleta ObjBase :
Destruindo a Parte da Base!
Deleta ObjDeriv :
Destruindo a Parte da Base!
Destruindo a Parte da Derivada!
Destruindo a Parte da Base!
Destruindo a Parte da Base!
• O que isto significa???? Se alocarmos objetos da classe derivada a
partir de ponteiros da classe base com destrutores não virtuais,
somente o destrutor da Parte Base é chamado apesar de ptrBase
estar apontando, no segundo caso para um objeto da classe
Derivada. Solução: o destrutor deve ser virtual, para ter o
comportamento esperado!
class Base {
public :
virtual ~Base() {cout << "\n\tDestruindo a Parte da Base!";}
};
Resultado:
Deleta ObjBase :
Destruindo a Parte da Base!
Deleta ObjDeriv :
Destruindo a Parte da Derivada!
Destruindo a Parte da Base!
Destruindo a Parte da Derivada!
Destruindo a Parte da Base!
Destruindo a Parte da Base!
Exemplo:Implementação da Hierarquia de Shape
Shape
virtual void draw();
Triangle
Square
Circle
void draw();
void draw();
void draw();
Programação Orientada a Objetos em C++ Pg. 28
class Point { /* ... */};
class Color{ /* ... */};
class Shape {
Point center;
Color col;
// ...
public:
Point where( ) const { return center;}
void move( Point to) { center = to; /* .... */ draw(); }
virtual void draw( ) { }; // Sim, vazia ...
virtual void rotate(float ang) { }; // Tambem ...
// ...
};
class Circle : public Shape {
real radius;
public:
void draw() { /* .... */}
// Não e' preciso redefinir where, move, nem mesmo rotate
...
};
class Square: public Shape {
real side;
public:
void draw() { /* .... */}
void rotate(float) {/* ... */ } // Redefinida
};
Shape* vpshape[30];
...
vpshape[0] = new Circle;
vpshape[1] = new Square;
real alpha=30;
...
for(int i=0; i < 30; i++)
{
vpshape[i]->draw();
vpshape[i]->rotate(alpha);
vpshape[i]->draw();
}
Programação Orientada a Objetos em C++ Pg. 29
Agora, se quisermos extender o programa, adicionando qualquer forma
nova (por exemplo um donut) , é só criar a classe correspondente
(derivada de Shape), programar os métodos que forem necessários na
classe derivada e pronto! Todas as partes do programa que aceitavam um
ponteiro para Shape ou uma referência para Shape aceitarão a nova
forma!
Tabela de funções virtuais
• Como o programa pode fazer a ligação objeto ó função em tempo de
execução?
• O C++ efetiva a ligação dinâmica graças à existência de uma tabela
contendo um ponteiro para cada função virtual de uma classe,
incluindo as funções virtuais herdadas de uma ou mais classes base à
Tabela de funções virtuais, TFV.
• Todo objeto de uma classe possui um ponteiro que o liga a esta tabela:
PTR_TFV. De posse deste ponteiro e da tabela, o programa pode
decidir qual função virtual deve chamar quando o objeto solicitar.
class ClBase{
public:
virtual void f1(int);
virtual void f2();
int f3();
};
class ClDeriv : public ClBase {
public:
void f2();
int f3(); // Não deveria estar fazendo isto ... !
virtual int f4(float);
};
void ClBase :: f1(int) { ... }
TFV de ClBase
void ClBase :: f2() { ... }
f1
f2
TFV de ClDeriv
f1
f2
f4
void ClDeriv :: f2(int) { ... }
void ClDeriv :: f4(float) { ... }
f3() não é virtual. Ela foi
sobrecarregada em ClDeriv,
mas não podemos identificar a
chamada em tempo de
execução, porque ela não está
na tabela de funções virtuais
Programação Orientada a Objetos em C++ Pg. 30
4.4. Classes Abstratas:
• No capítulo 2 vimos que existem Classes para as quais não podemos
declarar objetos: Classes Abstratas à servem somente como classes
base, para derivar outras classes.
• A classe Shape do item anterior é um exemplo típico de uma classe
cuja função é esta: servir somente para derivar as classes Circle,
Square, etc ...
• As Classes Abstratas são aquelas em que pelo menos uma de suas
funções não possui definição: funções virtuais puras.
class Classe_Abstrata{
...
virtual protótipo_da_função_virtual_pura() = 0;
};
• Qualquer tentativa de declaração de um objeto de uma classe Abstrata
gera um erro de compilação. Entretanto, é permitido declarar
referências e ponteiros para classes abstratas, desde que estes apontem
para objetos de classes derivadas na hierarquia e que, nelas, as funções
virtuais puras tenham todas sido definidas.
• Qual o propósito de se ter uma função virtual pura? à 1- Criar
uma classe abstrata; 2- Fazer com que as classes derivadas herdem
somente a interface da função;
• Qual o propósito de se ter funções virtuais (impuras)? à Fazer
com que as classes derivadas herdem a interface da função e uma
implementação default para aquela função;
• Qual o propósito de se ter funções não virtuais na classe Base? à
Fazer com que as classes derivadas herdem a interface da função e
uma implementação obrigatória para aquela função; Nunca redefina
uma função não virtual herdada, pois isto pode trazer sérios problemas,
quando estamos trabalhando com objetos dinâmicos: o polimorfismo
não funcionaria!
B x;
class A{
A *pA = &x; // Ponteiro para x;
public:
pa->mf(); // OOPS! Chama A::mf()
void mf();
...
B *pB = &x;
};
pb->mf(); // Chama B::mf()
class B: public A{
public:
void mf(); // Esconde A::mf() !!!
Programação Orientada a Objetos em C++ Pg. 31
...};
• è Devemos sempre fazer diferença entre herança da interface e
herança da implementação.
Classe Shape, revisitada ...
class Point { /* ... */};
class Color{ /* ... */};
class Shape {
// Classe Abstrata
Point center;
Color col;
// ...
public:
Point where( ) const { return center;}
void move( Point to) { center = to; /* .... */ draw(); }
virtual void draw( ) = 0 // Virtual pura
virtual void rotate(float ang)= 0; // Tambem ...
virtual bool is_closed ( ) = 0; // Tambem
// ...
};
class Circle : public Shape {
real radius;
public:
void draw() { /* .... */}
void rotate (float) { } // Vazia, precisa definir a funcao pura
bool is_closed( ) { return true;}
Circle (Point p, int r);
};
Uma classe que não redefine uma função virtual pura continua abstrata
herdada de uma classe base continua abstrata. Isto nos permite construir a
implementação em etapas:
class Poligon: public Shape {
public:
bool is_closed( ) { return true; }
// draw e rotate não foram redefinidas è Polígono é abstrata
};
Poligon b; // Erro: declaracao de um objeto de classe abstrata
class Irregular_polygon : public Poligon {
Programação Orientada a Objetos em C++ Pg. 32
list <Point> lp;
// Template que existe na STL
public:
void draw();
void rotate(float); // Não se tem mais funções virtuais puras
// ...
};
Irregular_polygon poly(alguns_pontos); // OK, se existir o
construtor
Um uso importante de classes abstratas é no fornecimento de uma
interface sem expor qualquer detalhe da implementação. Por exemplo,
um sistema operacional poderia esconder os detalhes dos seus
dispositivos físicos em uma classe abstrata:
class Device {
public:
virtual int open(char*, int, int) = 0;
virtual int close(int) = 0;
virtual int read(int, char*, int) = 0;
virtual int write(int, const char*, int) = 0;
virtual int ioctl(int, int ...) = 0;
virtual ~Device(); // Destrutor virtual
};
Poderíamos especificar drivers para dispositivos físicos como classes
derivadas de Device e manipular uma grande variedade de drivers através
da interface comum a todos eles
class Disk : public Device {
public:
int open(char*, int, int);
int close(int);
int read(int, char*, int);
int write(int, const char*, int);
int ioctl(int, int ...);
Disk();
~Disk();
};
Poderíamos também definir uma classe para os processos:
class Proc {
Programação Orientada a Objetos em C++ Pg. 33
public:
virtual int sleep (Sleepq*, int);
virtual void wakeup();
virtual int save_runstate();
virtual void resume_runstate();
virtual int send_msg(int);
Proc(int);
Proc (Proc &);
~Proc();
};
è Pela introdução de classes abstratas temos os mecanismos básicos
para escrever um programa completo de forma modular, utilizando
classes como nossos blocos de construção.
Exemplo de Projeto de Hierarquia de Classes
Seja um problema de projeto simples: fornecer uma maneira de um
usuário receber um número inteiro a partir da interface com o usuário.
• De modo a isolar o nosso programa da grande variedade de opções de
entrada que um sistema possa ter, e também para explorar as escolhas
de projeto, vamos começar definindo nosso modelo desta operação
simples de entrada.
• Atributos e métodos básicos: uma classe Ival_box que conheça a faixa
de valores de entrada que ela irá aceitar: um programa poderá pedir a
Ival_box o seu valor, pedir que ela apresente um prompt para o
usuário, se necessário, o programa poderá perguntar a Ival_box se o
usuário modificou o valor desde que o programa o consultou pela
última vez, etc ...
• Devido ao fato de que existem n maneiras de se implementar esta
idéia, assume-se que poderemos ter muitos tipos diferentes de
Ival_boxes, como controles deslizantes, caixas de texto, interação de
voz, etc ...
• A idéia básica é construir um sistema de interface virtual com o
usuário, que fornecerá vários dos serviços dos sistemas de interface
com o usuário existentes. Ele vai poder ser implementado numa ampla
variedade de formas, de modo a garantir a portabilidade do código de
aplicação.
Primeira abordagem: uma hierarquia de classes tradicional:
class Ival_box {
Programação Orientada a Objetos em C++ Pg. 34
protected:
int val;
int low, high;
bool changed;
public:
Ival_box(int ll, int hh) { changed = false; val = ll; high = hh;}
virtual int get_value() { changed=false; return val;}
virtual void set_value(int i) (changed=true; val =i;}
virtual void prompt( ) { }
virtual bool was_changed( ) const { return changed;}
};
Um programador usaria esta classe de forma semelhante a:
void interact (Ival_box* pb)
{
pb->prompt(); // alerta o usuário
// ...
int i = pb->get_value();
if(pb->was_changed()) {
// Novo valor; faça alguma coisa
}
else
// O valor antigo está OK, faça outra coisa
}
// ...
}
void fct()
{
Ival_box *p1 = new Ival_slider(0,5); // Ival_slider derivada
//de Ival_box
interact(p1);
Ival_box *p2 = new Ival_dial(1,12); // Idem Ival_dial
interact(p2);
}
• Não se está preocupado em saber como um programa espera pela
entrada do usuário: talvez se tenha uma função get_value(), talvez o
programa associe Ival_box com um evento e prepara-a para responder
a uma função callback, ou talvez o programa dispare uma thread para
o sistema de interface com o usuário... Isto é um detalhe de
implementação e não interfere no nosso projeto!
Programação Orientada a Objetos em C++ Pg. 35
class Ival_slider : public Ival_box{
// Parte gráfica, que define a aparência de slider, etc ...
public:
Ival_slider (int, int);
int get_value();
void prompt();
};
• Outras classes derivadas poderiam ser definidas: Ival_dial,
popup_ival_slider, flashing_ival_slider, etc ...
• De onde pegaríamos a parte gráfica: de uma biblioteca de classes
gráficas, por exemplo da “Big Bucks Inc” . Poderíamos fazer nossas
classes um tipo de BBwindow. Então:
class Ival_box : public BBwindow{/*...*/}; //agora usa BBwindow
class Ival_slider : public Ival_box{ /* ...*/};
class Ival_dial: public Ival_box {/* ... */};
class Flashing_ival_slider : public Ival_slider {/* ... */};
class Popup_ival_slider : public Ival_slider {/*...*/};
BBwindow
Ival_box
Ival_dial
Popup_ival_slide
r
Crítica a este projeto:
Ival_slider
Flashing_ival_slider
• O projeto funciona bem, porém tem alguns detalhes que podem ser
motivo de se procurar formas alternativas de implementação.
1. BBwindow como base de Ival_box: isto não está correto... Ival_box
implementa um conceito diferente do que é implementado por
Programação Orientada a Objetos em C++ Pg. 36
BBwindow. O fato de Ival_box ser uma janela de BBwindow é, na
realidade, um detalhe de implementação. Derivar Ival_box de
BBwindow eleva um detalhe de implementação a uma decisão de
projeto de primeiro nível! E se mudássemos a nossa classe de janelas
para as fornecidas por “Imperial Bananas” ou para “Liberated
Software” ou para “Compiler Whizzes”? Teríamos que manter
versões distintas do programa ... Pesadelo !
class Ival_box: public BBwindow {/*...*/};
class Ival_box: public CWwindow {/*...*/};
class Ival_box: public IBwindow {/*...*/};
class Ival_box: public LSwindow {/*...*/};
// versao BB
// versao CW
// versao IB
// versao LS
2. Toda classe derivada compartilha os dados básicos declarados em
Ival_box. Estes dados são um detalhe de implementação, e não
deveriam estar na interface de Ival_box. Por exemplo, um Ival_slider
não precisa do valor inteiro armazenado, pois este pode ser calculado
diretamente da posição do controle deslizante quando alguém executa
get_value(). Ter dois valores relacionados, mas diferentes, é fonte
permanente de erros! É melhor manter os dados privados, de forma
que os programadores das classes derivadas não possam criar
confusão com eles. Melhor ainda, porque não colocar os dados
somente nas classes derivadas, onde eles podem ser definidos de
forma a atingir os requisitos destas classes de forma exata?
Segunda abordagem: uma hierarquia com classes abstratas:
Objetivos:
• O sistema de interface com o usuário deve ser um detalhe de
implementação escondido dos usuários da classe que não precisam
saber sobre ele;
• A classe Ival_box não deve conter dados, fornecendo apenas uma
interface;
• Nenhum tipo de recompilação de código usando a família de classes
deve ser necessária depois de uma mudança no sistema de interface
com o usuário;
• Ival_boxes para diferentes sistemas de interface devem coexistir no
nosso sistema.
Ival_box como uma interface pura:
class Ival_box {
public:
virtual int get_value() = 0;
Programação Orientada a Objetos em C++ Pg. 37
virtual void set_value(int i) =0;
virtual void prompt( ) =0;
virtual bool was_changed( ) =0;
virtual ~Ival_box() { } // Destrutor virtual
};
class Ival_slider : public Ival_box, protected BBWindow {
public:
Ival_slider (int, int);
~Ival_slider();
int get_value();
void prompt();
bool was_changed() const;
protected:
// Funcoes que sobrescrevem funções virtuais de BBwindow
// p.ex. BBwindow::draw(), BBwindow::mouse1hit()
private:
//Dados necessários para slider
};
• BBwindow é somente um detalhe de implementação à protected
• Ival_box fornece a interface da implementação à pública
A hierarquia agora é definida da seguinte forma:
class Ival_box {/*...*/};
class Ival_slider : public Ival_box, protected BBwindow { /* ...*/};
class Ival_dial: public Ival_box, protected BBwindow {/* ... */};
class Flashing_ival_slider : public Ival_slider {/* ... */};
class Popup_ival_slider : public Ival_slider {/*...*/};
BBwindow
BBwindow
Ival_box
Ival_slider
Ival_dial
“Implementado usando”
ou
“Detalhe de implementação”
Popup_ival_slide
r
Flashing_ival_slider
Programação Orientada a Objetos em C++ Pg. 38
Terceira abordagem: uma implementação alternativa:
A implementação anterior está melhor, mas ainda não resolve o problema
de controle de diversas versões
class Ival_box {/*...*/};
// o que é comum
class Ival_slider : public Ival_box, protected BBwindow {/* ... */}
class Ival_slider : public Ival_box, protected CWwindow {/*... */}
A solução óbvia é definir várias classes Ival_slider, com nomes
separados:
class Ival_box {/*...*/};
// o que é comum
class BB_ival_slider : public Ival_box, protected BBwindow {/* ...
*/} class CW_ival_slider : public Ival_box, protected
CWwindow{/*... */}
Pode-se ainda refinar da seguinte forma:
class Ival_box {/*...*/};
class Ival_slider : public Ival_box {/* ... */ }; // o que é comum
class BB_ival_slider : public Ival_slider, protected BBwindow
{/*...*/}
class CW_ival_slider :public Ival_slider, protected CWwindow{/*.
*/}
Ival_box
BBwindow
Ival_slider
BB_ival_slider
CWwindow
Cw_ival_slider
Pode-se ainda refinar a solução, usando classes mais específicas na
hierarquia de implementação. Por exemplo, se a “Big Bucks Inc” possuir
sua própria classe slider, podemos derivar a nossa Ival_slider diretamente
de BB_slider:
class BB_ival_slider : public Ival_slider, protected BBslider {/*...*/}
class CW_ival_slider :public Ival_slider, protected CWslider{/*. */}
Programação Orientada a Objetos em C++ Pg. 39
BBwindow
Ival_box
CWwindow
BBslider
Ival_slider
CWslider
BB_ival_slider
Cw_ival_slider
Este melhoramento se torna significativo onde nossas abstrações não são
muito distintas das utilizadas pelo sistema utilizado para implementação.
A programação se transforma em um mapeamento entre conceitos
similares.
A hierarquia então se transformará em:
class Ival_box {/*...*/};
class Ival_slider : public Ival_box{ /* ...*/};
class Ival_dial: public Ival_box {/* ... */};
class Flashing_ival_slider : public Ival_slider {/* ... */};
class Popup_ival_slider : public Ival_slider {/*...*/};
seguida pelas implementações desta hierarquia para vários sistemas de
interface com o usuário, expressas como classes derivadas:
class BB_ival_slider: public Ival_slider, protected BBslider {/* ... */};
class BB_flashing_ival_slider :
public Flashing_ival_slider,
protected BBslider {/* ... */};
class BB_popup_ival_slider: public Popup_ival_slider,
protected BBslider {/* ... */};
class CW_ival_slider: public Ival_slider, protected CWslider {/* ... */};
// ...
• A hierarquia chegou em um ponto quase ideal. Conseguimos jogar
para a parte mais baixa as possíveis dependências de implementação .
• Um problema que resta é que a criação de objetos ainda deve ser feita
utilizando nomes dependentes da implementação, como CW_ival_dial
e BB_flashing_ival_slider. Se mudarmos de sistema de janelas,
deveremos “catar” no nosso código todas as ocorrências de
declarações de objetos de um tipo e substituí-los por outros.
Programação Orientada a Objetos em C++ Pg. 40
Quarta abordagem: introduzindo uma classe para as operações de
criação de objetos:
class Ival_maker {
public:
virtual Popup_ival_slider* popup_slider(int, int)=0; //cria popup
slider
virtual Ival_dial* dial(int, int) = 0; // cria um dial
// ...
};
class BB_maker : public Ival_maker {
// cria as versões BB
public:
Popup_ival_slider* popup_slider(int, int); //cria popup slider
virtual Ival_dial* dial(int, int); // cria um dial
// ...
};
class LS_maker : public Ival_maker {
// cria as versões LS
public:
Popup_ival_slider* popup_slider(int, int); //cria popup slider
virtual Ival_dial* dial(int, int); // cria um dial
// ...
};
Cada função cria um objeto do tipo desejado (interface e
implementação):
Ival_dial* BB_maker ::dial(int a, int b)
{
return new BB_ival_dial(a,b); }
Ival_dial* LS_maker :: dial(int a, int b)
{
return new LS_ival_dial(a,b); }
Dado um ponteiro para um Ival_maker, um usuário pode agora criar
objetos sem saber exatamente qual sistema de interface com o usuário
está sendo utilizado. Por exemplo:
void user(Ival_maker* pim)
{
Ival_box = pim->dial(0,99); // cria um objeto dial apropriado
// ...
}
BB_maker BB_impl;
// para usuários BB
Programação Orientada a Objetos em C++ Pg. 41
LS_maker LS_impl;
// para usuários LS
void driver()
{
user(&BB_impl); // usa BB
user(&LS_impl); // usa LS
}
Variante para a criação de objetos: “Construtores virtuais”.
class Expr {
public:
Expr();
// construtor default
Expr(const Expr&);
//construtor de cópia
virtual Expr* new_expr() {return new Expr();}
virtual Expr* clone() { return new Expr(*this);}
// ...
};
• new_expr() e clone() são virtuais e indiretamente constroem objetos:
por isto, são chamadas de “construtores virtuais”
• Uma classe derivada pode sobrescrever new_expr() e/ou clone() para
retornar um objeto de seu próprio tipo:
class Cond : public Expr {
public:
Cond();
Cond(const Cond&);
Cond* new_expr() {return new Cond();}
Cond* clone() { return new Cond(*this);}
// ...
};
Então
void user (Expr* p)
{
Expr* p2 = p->new_expr();
}
Se o objeto apontado por p for da classe Expr, p2 apontará para um
objeto da classe Expr; se o objeto apontado por p for da classe Cond, p2
apontará para um objeto da classe Cond! Os objetos também podem ser
copiados sem perda de informação, usando o método clone().
Download

4. Programação Orientada a Objetos em C++