Vous êtes sur la page 1sur 44

Funções virtuais e polimorfismo

Um Rei para governá-los. Um Rei para encontrá-los.


Um Rei para trazê-los e na escuridão guiá-los.
John Ronald Reuel Tolkien, The Fellowship ofthe Ring
O silêncio de pura inocência persuade quando a fala falha. William
Shakespeare, The Winter’s Tale
Proposições gerais não decidem casos concretos.
Oliver Wendell Holmes

eCe Faça

uillento “um 1 1 c
esse

lSões - e, Class
rarquja
formas

1
/

Objetivos

• Entender a noção de polimorfismo.


• Entender como declarar e usar funções virtual e aplicar o polimorfismo.
• Entender a diferença entre classes abstratas e classes concretas.
• Aprender como declarar funções virtual puras e criar classes abstratas.
• Apreciar como o polimorfismo torna os sistemas extensíveis e mais fáceis de
manter.
• Entender como funções virtual e a vinculação dinâmica são implementadas em
C++ “por baixo dos panos”.

Um filósofo de valor incontestável não pensa no vácuo. Mesmo suas idéias mais
abstratas são, até certo ponto, condicionadas pelo que é ou não é conhecido na
época eín que ele vive.
Alfred North Whitehead

608 C++ COMO PROGRAMAR


Visão geral ____________
10.1 Introdução
10.2 Campos de tipo e comandos switch
10.3 Funções virtual
10.4 Classes base abstratas e classes concretas
10.5 Polimorfismo
10.6 Estudo de caso: um sistema de folha de pagamento usando polimorfismo
10.7 Novas classes e vinculação dinâmica
10.8 Destruidores virtual
10.9 Estudo de caso: herdando a interface e herdando a implementação
10.10 Polimorfismo, funções virtual e vinculação dinâmica “vistos por dentro”
Resumo . 7’rminologia . Erros comuns de programação Boas práticas de
programação Dicas de desempenho Observações de engenharia de software
Exercício de auto-revisão Respostas ao exercício de auto-revisão • Exercícios
10.1 Introdução
Com funções virtuais e polimorfismo é possível se projetar e implementar
sistemas que são mais facilmente extensíveis. Os programas podem ser escritos
para processar genericamente - como objetos de classes base - objetos de
todas as classes existentes em uma hierarquia. Classes que não existem
durante o desenvolvimento do programa podem ser acrescentadas, com poucas
ou nenhuma modificação, à parte genérica do programa - desde que aquelas
classes sejam parte da hierarquia que está sendo processada genericamente.
As ánicas partes de um programa que necessitarão de modificações são
aquelas partes que exigem conhecimento direto da classe particular que está
sendo acrescentada à hierarquia.
10.2 Campos de tipo e comandos switch
Um meio de lidar com objetos de tipos diferentes é usar um comando switch
para executar a ação apropriada sobre cada objeto, com base no tipo do objeto.
Por exemplo, em uma hierarquia de formas, em que cada forma especifica seu
tipo como um membro de dados, uma estrutura switch poderia determinar qual
função de impressão chamar baseando-se no tipo do objeto particular.
Existem muitos problemas com o uso de lógica de programação que emprega
switchs. O programador pode se esquecer de fazer um teste de tipo quando
necessário. O programador pode esquecer de testar todos os casos possíveis
em um switch. Se um sistema baseado em switchs for modificado pelo
acréscimo de novos tipos, o programador pode se esquecer de inserir os novos
casos em todos os comandos switch existentes. Todas as adições ou exclusões
a uma classe para tratar novos tipos exige que todo comando switch no sistema
seja modificado; localizar todos eles pode consumir tempo e é uma atividade
sujeita a erros.
Como veremos, funções virtual e a programação polimórfica podem eliminar a
necessidade do uso de lógica de programação que emprega switchs. O
programador pode usar o mecanismo de função virtual para executar
automaticamente a lógica equivalente, evitando deste modo os tipos de erros
tipicamente associados com o uso de lógica de programação que emprega
switchs.
Observação de engenharia de software 10.1
______ Uma conseqüência interessante do uso de funções virtual e
polimorfismo é que os programas adquirem uma aparência mais simples. Eles
contêm menos ramificações lógicas, favorecendo o emprego de código
seqüencial mais simples. Isso facilita o teste, a depura ção e a manutenção de
programas e evita erros.

CAPÍTULO 10 - FUNÇÕES VIRTUAIS E POLIM0RFISM0 609

Suponha que um conjunto de classes de formas, tais como Circle, Triangle.


Rectangle. Square, etc. sejam todas derivadas da classe base Shape. Na
programação orientada a objetos, cada uma dessas classes poderia ser dotada
da habilidade de desenhar a si própria. Embora cada classe tenha sua própria
função draw. a função draw para cada forma é bastante diferente. Quando
desenharmos uma forma, qualquer que seja a forma, seria interessante sermos
capazes de tratar todas essas formas genericamente, como objetos da classe
básica Shape. Então, para desenhar qualquer forma, poderíamos simplesmente
chamar a função draw da classe base Shape e deixar o programa determinar
dinamicamente (i.e., durante a execução) qual a função draw de uma classe
derivada que deve ser usada.
Para possibilitar este tipo de comportamento, declaramos draw na classe básica
como uma função virtual e sobrescrevemos draw em cada uma das classes
derivadas para desenhar a forma apropriada. Uma função virtual é declarada
precedendo-se o protótipo da função pela palavra-chave virtual na classe básica.
Por exemplo,
virtual void draw() const;
pode aparecer na classe base Shape. O protótipo precedente declara que a
função draw é uma função constante, que não aceita nenhum argumento, não
retorna nada e é uma função virtual.
Observação de engenharia de software 10.2
______ Uma vez que uma função seja declarada virtual, ela permanece virtual
por toda a hierarquia de herança, daquele ponto para baixo, mesmo se não é
declarada virtual quando uma classe a sobrescreve.
Boa prática de programação 10.1
Embora certas funções sejam implicitamente virtual, por causa de uma
declaração feita mais acima na hierarquia de classes, declarar explicitamente
estas funções como virtual em cada nível da hierarquia melhora a clareza do
programa.
Observação de engenharia de software 10.3
Quando uma classe derivada escolhe não definir uma função como virtual, a
classe derivada simplesmente herda a definição da função virtual da sua classe
básica imediata.
Se a função draw na classe básica foi declarada virtual, e se então usamos um
ponteiro ou referência da classe básica para apontar para o objeto da classe
derivada e invocar a função draw usando este ponteiro ou referência (por
exemplo, shapePtr->draw () ), o programa escolherá dinamicamente a função
draw correta para a classe derivada (i.e., durante a execução) com base no tipo
do objeto - não no tipo do ponteiro ou da referência. Tal vincula ção dinâmica
será ilustrada nos estudos de caso nas Seções 10.6 e 10.9.
Quando uma função virtual é chamada referenciando um objeto específico por
nome e usando o operador
de seleção de membro ponto (.) (por exemplo, squareObject. draw ), a referência
é resolvida durante a
compilação (isto é chamado de vincula ção estática), e a função virtual que é
chamada é aquela definida para a
(ou herdada pela) classe daquele objeto particular.
10.4 Classes base abstratas e classes concretas
Quando pensamos em uma classe como um tipo, assumimos que os objetos
daquele tipo serão instanciados. Contudo, existem casos em que é útil se definir
classes para as quais o programador nunca pretende instanciar nenhum objeto.
Tais classes são chamadas de classes abstratas. Como elas são usadas como
classes base em situações de herança, normalmente nos referiremos a elas
como classes básicas abstratas. Nenhum objeto de uma classe básica abstrata
pode ser instanciado.
O único objetivo de uma classe abstrata é fornecer uma classe básica
apropriada da qual outras classes podem herdar a interface e/ou a
implementação. As classes das quais podem ser instanciados objetos são
chamadas de classes concretas.
Poderíamos ter uma classe básica abstrata TwoDimensionalShape e classes
concretas derivadas tais como Square, Circle. Triangle. etc. Poderíamos também
ter uma classe básica abstrata

10.3 Funções virtual

610 C++ COMO PROGRAMAR


ThreeDimensionalShape e classes concretas derivadas tais como Cube, Sphere,
Cylinder. etc. Classes base abstratas são muito genéricas para definirmos
objetos reais; precisamos ser mais específicos antes de podermos pensar em
instanciar objetos. Isso é o que as classes concretas fazem; elas fornecem os
aspectos específicos que tornam razoável instanciarmos objetos.
Uma classe é tornada abstrata declarando-se uma ou mais de suas funções
virtual como sendo “pura”. Uma
função virtual pura é uma função que tem um inicializador = O em sua
declaração, como em
virtual double earnings() const = O; II virtual pura
Observação de engenharia de software 10.4
______ Se uma classe é derivada de uma classe com uma Junção virtual pura e
se nenhuma definição for fornecida para aquela função virtual pura na classe
derivada, então aquela função virtual permanece pura na classe derivada.
Conseqüentemente, a classe derivada também é uma classe abstrata.
Erro comum de programação 10. 1
Tentar instanciar um objeto de uma classe abstrata (i.e., uma classe que contém
uma ou mais funções
virtual puras) é um erro de sintaxe.
Uma hierarquia não necessita conter quaisquer classes abstratas, mas, como
veremos, muitos sistemas bons orientados a objetos têm hierarquias de classe
com uma classe básica abstrata no topo. Em alguns casos, as classes abstratas
formam os níveis mais altos da hierarquia. Um bom exemplo disso é uma
hierarquia de formas. A hierarquia poderia ser encabeçada pela classe básica
abstrata Shape. No próximo nível abaixo, podemos ter mais duas classes
básicas abstratas, isto é TwoDimensionalShape e ThreeDimensionalShape. O
próximo nível abaixo começaria a definir classes concretas para formas
bidimensionais, tais como cfrculos e quadrados, e classes concretas para formas
tridimensionais, tais como esferas e cubos.
10.5 Polimorfismo
C++ possibilita o polimoifismo - a habilidade de objetos de classes diferentes
relacionadas por herança responderem diferentemente à mesma mensagem
(i.e., uma chamada a uma função membro). A mesma mensagem enviada para
muitos tipos diferentes de objetos assume “muitas formas” - daí o termo
polimorfismo. Se, por exemplo, a classe
Rectangle é derivada da classe Quadrilateral, então um objeto Rectangle é uma
versão mais específica de “7 um objeto Quadrilateral. Uma operação (tal como
calcular o perímetro ou a área) que pode ser executada sobre
um objeto do tipo Quadrilateral também pode ser executada sobre um objeto do
tipo Rectangle.
O polimorfismo é implementado por meio de funções virtuais. Quando é feito um
pedido através de um ponteiro (ou referência) de uma classe básica para usar
uma função virtual, C++ escolhe a função sobrecarregada correta na classe
derivada apropriada associada com o objeto.
As vezes, uma função membro não-virtual é definida em uma classe básica e
sobrescrita em uma classe derivada. Se uma função membro como esta é
chamada, por meio de um ponteiro da classe básica para o objeto da classe
derivada, a versão da classe básica é usada. Se a função membro é chamada
por meio de um ponteiro da classe derivada, a versão da classe derivada é
usada. Isto é comportamento não-polimórfico.
Considere o exemplo seguinte, que usa a classe base Ernployee e a classe
derivada HourlyWorker da Fig.
9.5:
Employee e, *eptr =
HourlyWorker h, *hptr =
ePtr->printO; II chama a função print da classe base
hPtr->printQ; // chama a função print da classe derivada
ePtr = &h; // conversão implícita permissível
ePtr->print II ainda chama print da classe base
Nossa classe base Employee e nossa classe derivada HourlyWorker tiveram
ambas suas próprias funções de 1 impressão definidas. Como as funções não
foram declaradas como virtual e têm a mesma assinatura, chamar a

CAPÍTULO 10- FUNÇÕES VIRTUAIS E POLIMORFISMO 611

função print através de um ponteiro de Employee resulta em chamar Employee: :


print O (não importando se o ponteiro de Employee está apontando para um
objeto da classe base Employee ou para um objeto da classe derivada
HourlyWorker) e chamar a função de impressão através de um ponteiro de
HourJ.yWorker resulta em chamar Hourj.yWorker: print . A função print da classe
base também está disponível para a classe derivada, mas para chamar print da
classe base para um objeto da classe derivada através de um ponteiro para um
objeto da classe derivada, por exemplo, a função deve ser explicitamente
chamada como segue:

hPtr->Employee: :printO; II chama função print da classe base

Isto especifica que print da classe básica deve ser chamada explicitamente.
Através do uso de funções virtuais e polimorfismo, uma chamada de função
membro pode causar a execução de ações diferentes, dependendo do tipo do
objeto que está recebendo a chamada (veremos que é necessária uma pequena
quantidade de overhead durante a execução). Isto dá ao programador uma
tremenda capacidade de expressão. Veremos vários exemplos do poder do
polimorfismo e das funções virtuais nas próximas seções.

Observação de engenharia de software 10.5

Com funções virtual e polimoifismo, o programador pode tratar com


generalidades e deixar para o ambiente de execução a preocupação com os
aspectos específicos. O programador pode fazer com que uma grande
variedade de objetos se comporte de modos apropriados para aqueles objetos,
sem sequer conhecer os tipos dos mesmos.

Observação de engenharia de software 10.6

O polimorfismo promove a extensibilidade: o software escrito para invocar o


comportamento polimótfico
é escrito independentemente dos tipos dos objetos para os quais as mensagens
são enviadas. Deste modo,
novos tipos de objetos que podem responder a mensagens existentes podem
ser acrescentados a um sistema como esse, sem modificar o sistema básico.
Com a exceção do código cliente que instancia novos
objetos, os programas não necessitam ser recompilados.
Observação de engenhariti de software 10. 7

Uma classe abstrata define uma interface para os vários membros de uma
hierarquia de classes. A classe abstrata contém funções virtuais puras que serão
definidas nas classes derivadas. Todas as funções na hierarquia podem usar
essa mesma interface, através do polimorfismo.

Embora não possamos instanciar objetos de classes base abstratas, podemos


declarar ponteiros e referências para classes base abstratas. Tais ponteiros e
referências podem, então, ser usados para possibilitar manipulações
polimórficas de objetos de classes derivadas quando tais objetos forem
instanciados a partir de classes concretas.
Vamos agora considerar as aplicações do polimorfismo e funções virtual. Um
gerenciador de tela necessita exibir muitos objetos de classes diferentes,
inclusive novos tipos de objetos que serão incorporados ao sistema até mesmo
depois de o gerenciador de tela ser escrito, O sistema pode necessitar exibir
várias formas (i.e., a classe base é Shape) tais como quadrados, círculos,
triângulos, retângulos, pontos, linhas, etc. (cada uma dessas classes de formas
é derivada da classe base Shape). O gerenciador de tela usa ponteiros da
classe base ou referências (para Shape) para administrar todos os objetos a
serem exibidos. Para desenhar qualquer objeto (não importando o nível em que
o objeto aparece na hierarquia de herança), o gerenciador de tela usa um
ponteiro da classe base (ou referência) para o objeto e simplesmente envia a
mensagem draw para o objeto. A função draw foi declarada como virtual pura na
classe base Shape e foi redefinida em cada uma das classes derivadas. Cada
objeto do tipo Shape sabe como desenhar a si mesmo adequadamente. O
gerenciador de tela não tem que se preocupar com de que tipo cada objeto é ou
se o objeto é de um tipo que o gerenciador de tela já viu antes - o gerenciador
de tela simplesmente diz a cada objeto para se desenhar adequadamente.
O polimorfismo é particularmente efetivo para implementar sistemas de software
em camadas. Em sistemas operacionais, por exemplo, cada tipo de dispositivo
físico pode operar de modo diferente dos outros. Independemente disso, os
comandos para ler ou escrever dados de e para dispositivos podem ter uma
certa uniformidade. A mensagem escrever enviada para um objeto acionador de
dispositivo precisa ser especificamente interpretada no contexto daqsuele
acionador de dispositivo e como aquele acionador de dispositivo manipula
dispositivos de um tipo específico. Porém, a chamada escrever, em si, não é
realmente diferente da escrever para qualquer outro dispositivo no

612 C++ COMO PROGRAMAR


sistema - ela simplesmente transfere um certo número de bytes da memória
para aquele dispositivo. Um sistema operacional orientado a objetos poderia
usar uma classe base abstrata para fornecer uma interface apropriada para
todos os acionadores de dispositivos. Então, por herança daquela classe base
abstrata, classes derivadas são formadas, todas operando de modo semelhante.
Os recursos (i.e., a interface public) oferecidos pelos acionadores de dispositivos
são fornecidos como funções virtual puras na classe base. As implementações
destas funções virtual são fornecidas nas classes derivadas que correspondem
aos tipos específicos de acionadores de dispositivos.
Com programação polimórfica, um programa poderia caminhar através de um
contêiner, tal como um array de ponteiros para objetos de vários níveis de uma
hierarquia de classes. Os ponteiros em tal array seriam todos ponteiros da
classe base para objetos de classes derivadas. Por exemplo, um array de
objetos da classe TwoDimensionalShape poderia conter ponteiros
TwoDimensionalShape * para objetos das classes derivadas Square. Circle,
Triangle. Rectangle. Line, etc. Enviar uma mensagem para desenhar cada objeto
do array iria, usando polimorfismo, desenhar a imagem correta na tela.
10.6 Estudo de caso: um sistema de folha de pagamento usando polimorfismo
Iremos usar funções virtual e polimorfismo para executar os cálculos de uma
folha de pagamento, com base no tipo de um empregado (Fig. 10.1). Usaremos
uma classe base Employee. As classes derivadas de Employee são Boss, ao
qual é pago um salário semanal fixo não importando o número de horas
trabalhadas, ComissionWorker. que recebe um salário básico fixo mais uma
porcentagem sobre as vendas, PieceWorker. o qual recebe pelo número de itens
produzidos, e HourlyWorker, que é pago por hora (normal) e hora extra.
Uma chamada à função earnings certamente se aplica genericamente a todos
os empregados. Mas o modo como o salário de cada pessoa é calculado
depende da classe do empregado, e tais classes são todas derivadas da classe
base Eniployee. Assim. earnings é declarada virtual pura na classe base
Employee. e implementações apropriadas de earnings são fornecidas para cada
uma das classes derivadas. Então, para calcular o salário de qualquer
empregado, o programa simplesmente usa um ponteiro (ou referência) da classe
base para o objeto daquele Employee e invoca a função earnings. Em um
sistema de folha de pagamento real, os diversos objetos do tipo empregado
poderiam ser apontados por elementos individuais em um array (ou lista) de
ponteiros do tipo Employee
*. O programa simplesmente percorreria o array, um elemento de cada vez,
usando os ponteiros Employee * para invocar a função earnings de cada objeto.
1 II Fig. 10.1: employ2.h
2 // Classe base abstrata Employee
3 #ifndef EMPLOY2H
4 #define EMPLOY2H
5
6 class Employee
7 public:
8 Employee( const char , const char *
9 -Emp1oyeeQ; // destruidor recupera memória
10 const char *getFirstwalne() const;
11 const char *getLastNe() const;
12
13 II Função virtual pura torna Employee urna classe base abstrata
14 virtual double earnings() const = 0; // virtual pura
15 virtual void print() const; II virtual
16 private:
17 char *fjrstNe;
18 char *lastNe;
19
20
21 #endif
Fig. 10.1 Demonstrando o polimorfismo com a hierarquia de classes Employee -
employ2 .h.

CAPÍTULO 10- FUNÇÕES VIRTUAIS E POLIMORFISMO 613


22 II Fig. 10.1: employ2.cpp
23 II Definições de funções membro para
24 II a classe base abstrata Employee.
25 II Nota: nenhuma definição dada para funções virtuais puras.
26 #include <iostream>
27
28 using std: :cout;
29
30 #include <cstring>
31 #include <cassert>
32 #include “employ2.h”
33
34 II Construtor aloca dinamicamente espaço para
35 II primeiro e último nomes e usa strcpy para
36 II copiar o primeiro e últimos nomes para o objeto.
37 Employee::Employee( const char *fjrst, const char *last
38
39 firstName = new char[ strlen( first ) + 1 1;
40 assert( firstName != O ) // testa se new funcionou
41 strcpy( firstName, first );
42
43 lastName = new char[ strlen( last ) + 1 1;
44 assert( lastName != O ) II testa se new funcionou
45 strcpy( lastName, last );
46
47
48 II Destruidor desaloca memória alocada dinamicamente
49 Employee: :-Employee()
50 {
51 delete [] firstwame;
52 delete [] lastName;
53 1
54
55 // Retorna um ponteiro para o primeiro nome
56 II Tipo de retorno const impede chamador de modificar dados privados.
57 II Chamador deve copiar o string retornado antes que o destruidor
58 II delete a memória dinâmica, para evitar um ponteiro indefinido.
59 const char *Employee: :getFirstName() const
60
61 return firstName; // chamador deve deletar memória
62
63
64 II Retorna um ponteiro para o último nome
65 // Tipo de retorno const impede chamador de modificar dados privados.
66 II Chamador deve copiar o string retornado antes que o destruidor
67 II delete a memória dinâmica, para evitar um ponteiro indefinido.
68 const char *Employee: :getLastName() const
69
70 return lastName; 1/ chamador deve deletar memória
71
72
73 II Imprime o nome do empregado
74 void Employee: :print() const
75 { cout « firstName « « lastwame;
Fig. 10.1 Demonstrando o polimorfismo com a hierarquia de classes Employee -
employ2 .cpp.

614 C++ COMO PROGRAMAR


76 II Fig. 10.1: bossl.h
77 II Classe Boss derivada a partir de Employee
78 #ifndef BOSS1H
79 #define BOSS1H
80 #include “employ2.h”
81
82 class Boss : public Employee
83 public:
84 Boss( const char , const char , double = 0.0 );
85 void setWeeklySalary( double );
86 virtual double earnings() const;
87 virtual void print() const;
88 private:
89 double weeklySalary;
90
91
92 #endif
Fig. 10.1 Demonstrando o polimorfismo com a hierarquia de classes Employee -
bossl . h.
93 II Fig. 10.1: bossl.cpp
94 // Definições de funções membro para a classe Boss
95 #include <iostream>
96
97 using std::cout;
98
99 #include “bossl.h”
100
101 II Função construtor para a classe Boss
102 Boss: :Boss( const char *first, const char *last, double s
103 : Employee( first, last ) II chama construtor da classe base
104 { setWeeklySalary( s ); }
105
106 // Incializa o salário de Boss
107 void Boss::setWeeklySalary( double s
108 { weeklySalary = s > O ? s : 0; }
109
110 II Obtém o pagamento de Boss
111 double Boss: :earnings() const { return weeklySalary; )
112
113 II Imprime o nome do Boss
114 void Boss::print() const
115 {
116 cout « “\n Chefe:
117 Employee: :printO;
118}
Fig. 10.1 Demonstrando o polimorfismo com a hierarquia de classes Employee -
bossi .cpp.
119 II Fig. 10.1: commisl.h
120 // Classe CommissionWorker derivada de Employee
121 #ifndef COMMIS1H
122 #define COMMIS1H
123 #include “employ2.h”
124
Fig. 10.1 Demonstrando o polimorfismo com a hierarquia de classes Employee -
commisl .h.

CAPÍTULO 10- FUNÇÕES VIRTUAIS E POLIMORFISMO 615

125 class CommissionWorker : public Employee


126 public:
127 CommissionWorker( const char , const char ,
128 double = 0.0, double = 0.0,
129 int0);
130 void setSalary( double );
131 void setCommission( double );
132 void setQuantity( int );
133 virtual double earnings() const;
134 virtual void print() const;
135 private:
136 double salary;
137 double cornmission;
138 int quantity;
139 };
140
141 #endif
Fig. 10.1 Demonstrando o polimortismo com a hierarquia de classes Employee -
commisl . h (parte 2 de 2).
Consideremos a classe Employee (linhas 1 a 75). As funções membro public
incluem um construtor que aceita o primeiro e último nome como parâmetros;
um destruidor que recupera a memória alocada dinamicamente; uma função get
que retorna o primeiro nome; uma função get que retoma o último nome; uma
função virtual pura carnings e uma função virtual print. Por que earnings é virtual
pura? A resposta é que não faz sentido fornecer uma implementação desta
função na classe Employee. Não podemos calcular o salário para um
empregado genérico - primeiro devemos saber que tipo de empregado ele é.
Tornando esta função virtual pura, estamos indicando que forneceremos uma
implementação desta função em cada classe derivada, mas não na própria
classe base. O programador nunca pretende chamar esta função virtual pura na
classe base abstrata Employee; todas as classes derivadas redefinirão earnings
com implementações apropriadas para aquelas classes.
A classe Boss (linhas 76 a 118) é derivada de Employee por herança pública. As
funções membro public incluem um construtor que aceita um primeiro nome, um
último nome e um salário semanal como parâmetros e passa o primeiro nome e
o último nome para o construtor de Employee para inicializar os membros
firstName e las tName da parte da classe base do objeto da classe derivada;
uma função set para atribuir um novo valor ao membro privado de dados
weeklySalary; uma função virtual earnings definindo como calcular o salário de
um Boss; e uma função virtual print que imprime o tipo do empregado e então
chama Employee: : print O para imprimir o nome do empregado.
142 // Fig. 10.1: commisl.cpp
143 // Definições de funções membro para a classe CommissionWorker
144 #include <iostream>
145
146 using std::cout;
147
148 #include “commisl.h”
II Construtor para a classe CommissionWorker
CoxnmissionWorker: :CommissionWorker( const char *first,
const char *last, double s, double c, int q
Employee( first, last ) II chama construtor da classe base
setSalary( s );
setCommission( c );
setQuantity( q );

// salário base semanal


II valor por item vendido
// total de itens vendidos na semana

149
150
151
152
153
154 {
155
156
157

Fig. 10.1 Demonstrando o polimorfismo com a hierarquia de classes Employee -


commisl . cpp (parte
1 de 2).

616 C++ COMO PROGRAMAR


158 }
159
160 // Inicializa o salário base semanal do CommissionWorker
161 void CommissionWorker::setSalary( double s
162 { salary = s > O ? s : O; }
163
164 // Inicializa a comissão do CommissionWorker
165 void Commissionworker: :setCommission( double e
166 { commission = c > O ? c : O;
167
168 // Inicializa a quantidade vendida do CommissionWorker
169 void CommissionWorker::setQuantity( int q
170 { quantity = q > O ? q : 0; }
171
172 // Determina os rendimentos do CommissionWorker
173 double CommissionWorker: : earnings () const
174 { return salary + commission * quantity; }
175
176 // Imprime o nome do CommissionWorker
177 void CommissionWorker: :print() const
178 {
179 cout « “\nComissionado: “;
180 Employee: :printO;
181 }
Fig. 10.1 Demonstrando o polimorfismo com a hierarquia de classes Employee -
commisl . cpp (parte 2 de 2).
A classe Comissionworker (linhas 119 a 181) é derivada de Employee por
herança public. As funções membro public incluem um construtor que aceita um
primeiro nome, um último nome, um salário, uma comissão e uma quantidade de
itens vendidos como parâmetros e passa o primeiro nome e o último nome para
o construtor de Employee: funções set para atribuir novos valores aos membros
de dados private salário, comissão e quantidade; uma função virtual earnings
definindo como calcular o salário de um ComissionWorker; e uma função virtual
de impressão (print), que imprime o tipo do empregado e então chama
Employee: : print para imprimir o nome do empregado.
182 II Fig. 10.1: piecel.h
183 // Classe PieceWorker derivada de Employee
184 #ifndef PIECE1H
185 #define PIECE1H
186 #include employ2.h’
187
188 class PieceWorker : public Employee {
189 public:
190 Pieceworker( const char , const char ,
191 double = 0.0, int = 0);
192 void setWage( double );
193 void setQuantity( int );
194 virtual double earnings() const;
195 virtual void print() const;
196 private:
197 double wagePerPiece; // remuneração por cada peça produzida
198 int quantity; II produção da semana
199 );
200
201 #endif
Fig. 10.1 Demonstrando o polimorfismo com a hierarquia de classes Employee -
piecel .h.

CAPÍTULO 10- FUNÇÕES VIRTUAIS E POLIMORFISMO 617


202 II Fig. 10.1: piecel.cpp
203 // Definições de funções membro para a classe PieceWorker
204 #include <iostream>
205
206 using std: :cout;
207
208 #include ‘piecel.h”
209
210 // Construtor para a classe PieceWorker
211 PieceWorker: :PieceWorker( const char *first, const char *last,
212 double w, int q
213 : Employee( first, last ) // chama construtor da classe base
214 {
215 setWage( w );
216 setQuantity( q );
217 }
218
219 II Inicializa a remuneração
220 void PieceWorker::setWage( double w
221 { wagePerPiece = w > O ? w : 0; }
222
223 II Inicializa o numero de itens produzidos
224 void PieceWorker::setQuantity( int q
225 { quantity = q > O ? q : 0; }
226
227 II Determina os rendimentos do PieceWorker
228 double PieceWorker: :earnings() const
229 { return quantity * wagePerPiece; }
230
231 II Imprime o nome do PieceWorker
232 void PieceWorker::print() const
233 {
234 cout « “\n PieceWorker: “;
235 Employee: :printO;
236 )
Fig. 10.1 Demonstrando o polimorfismo com a hierarquia de classes Employee -
piecel cpp.
237 II Fig. 10.1: hourlyl.h
238 II Definição da classe HourlyWorker
239 #ifndef HOURLY1H
240 #define HOURLY1H
241 #include “employ2.h”
242
243 class HourlyWorker : public Employee {
244 public:
245 HourlyWorker( const char , const char ,
246 double = 0.0, double = 0.0);
247 void setWage( double );
248 void setHours( double );
249 virtual double earnings() const;
250 virtual void print() const;
251 private:
252 double wage; II salário-hora
253 double hours; // horas trabalhadas na semana
254 };
255
256 #endif
Fig. 10.1 Demonstrando o polimorfismo com a hierarquia de classes Employee -
hourlyl .h.

618 C++ COMO PROGRAMAR


A classe PieceWorker (linhas 182 a 236) é derivada de Employee por herança
public. As funções membro public incluem um construtor que aceita um primeiro
nome, um último nome, uma remuneração por peça e uma quantidade de itens
produzidos como parâmetros e passa o primeiro e último nomes para o
construtor de Employee:
funções set para atribuir novos valores aos membros de dados private
wagePerPiece e quantity: uma função virtual earnings definindo como calcular o
salário de um PieceWorker: e uma função de impressão virtual que imprime o
tipo do empregado e então chama Employee: : print () para imprimir o nome do
empregado.
A classe HourlyWorker (linhas 237 a 297) é derivada de Employee por herança
public. As funções membro public incluem um construtor que aceita um primeiro
nome, um último nome, uma remuneração e o número de horas trabalhadas
como parâmetros e passa o primeiro nome e último nome para o construtor de
Employee para inicializar os membros firstName e las tNaxne da parte da classe
base do objeto da classe derivada funções de inicialização para atribuir novos
valores aos membros de dados privados wage e hours: uma função virtual
earnings definindo como calcular o salário de um HourlyWorker; e uma função
de impressão virtual que imprime o tipo do empregado e então chama
Employee: : print () para imprimir o nome do empregado.
O programa de teste é mostrado nas linhas 298 a 367. Cada um dos quatro
segmentos de código em main é semelhante, de modo que discutiremos
somente o primeiro segmento, que lida com um objeto do tipo Boss.

257
258
259
260
261
262
1 263
264
1 265
4 266
267
268
269
270 {
271
272
273 )
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290 }
291
292
293
294
295
296
297

setWage( w ); setHours ( h );

cout « “\n
Employee: :printO;

II Fig. 10.1: hourlyl.cpp


// Definições de funções membro para a classe HourlyWorker #include
<iostream>
using std::cout;
#include “hourlyl .h”
II Construtor para a classe HourlyWorker
HourlyWorker: :HourlyWorker( const char *first,
const char *last,
double w, double h
Employee( first, last ) // chama construtor da classe base
1/ Inicializa a remuneração
void HourlyWorker: :setWage( double w
wage = w > O ? w : 0;
// Inicializa horas trabalhadas
void HourlyWorker::setHours( double h
hours h > O && h < 168 ? h : 0;
// Obtém o pagamento do HourlyWorker
double HourlyWorker: :earnings() const
if ( hours <= 40 ) // sem horas extras
return wage * hours;
else // hora extra é paga com remuneração * 1.5 return 40 * wage + ( hours - 40 )
* wage * 1.5;
// Imprime o nome do HourlyWorker
void HourlyWorker: :print() const
HourlyWorker:

1
1

Fig. 10.1 Demonstrando o polimorfismo com a hierarquia de classes Employee -


hourlyl . cpp.

CAPÍTULO 10-FUNÇÕES VIRTUAIS E POLIMORFIsMO 619


II Fig. 10.1: figlOOl.cpp
II Programa de teste para a hierarquia Employee
#include <iostream>
using std: :cout;
using std::endl;
305 #include <iomanip>
306
307 using std::ios;
using std: :setiosflags;
using std: :setprecision;
#include “employ2 .h”
#include “bossi .h”
#include “commisl h”
#include “piecel .h”
#include “hourlyl.h”
II define formatação da saída
cout « setiosflags ( ios: : fixed 1 ios: : showpoint
« setprecision( 2 );
CommissionWorker c( Simone”, “Bianchi”, 200.0, 3.0, 150 );
c.printO; II vinculação estática
cout « “ recebeu $“ « c.earnings II vinculação estática
virtualViaPointer( &c ); // usa vinculação dinâmica
virtualViaReference( c ); // usa vinculação dinâmica
PieceWorker p( “Roberto’, “Santos’, 2.5, 200 );
p.printO; // vinculação estática
cout « “ recebeu $“ « p.earningsO; // vinculação estática
virtualViaPointer( &p ); // usa vinculação dinâmica
virtualViaReference( p ); II usa vinculação dinâmica
HourlyWorker h( “Carmem”, ‘Pereira’, 13.75, 40 );
h.printO; II
cout « “ recebeu $“ « h.earnings //
virtualViaPointer( &h ); II
virtualViaReference( h ); II
cout « endl;
return 0;

298
299
300
301
302
303
304

308
309
310
311
312
313
314
315
316

void virtualViaPointer( const Employee * void virtualViaReference ( const


Employee & );

317
318
319
320 int main()
321
322
323
324
325

Boss b( “José”, “Silva, 800.00 ); b.printO;


cout « “ recebeu $“ « b.earningsO); virtualViaPointer( &b );
virtualViaReference ( b );

II vinculação estática
II vinculação estática
// usa vinculação dinâmica
// usa vinculação dinâmica

326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353

vinculação estática vinculação estática usa vinculação dinâmica usa vinculação


dinâmica

II Faz chamadas virtuais com um ponteiro de

Fig. 10.1 Demonstrando o polimorfismo com a hierarquia de classes Employee -


figlO_Ol . cpp (parte 1 de 2).

620 C++ COMO PROGRAMAR


354 // classe base usando vinculação dinâmica.
355 void virtualViaPointer( const Employee *baseclassptr
356
357 baseClassPtr->print
358 cout « “ recebeu $“ « baseClassPtr->earnings
359 }
360
361 II Faz chamadas virtuais com urna referência para
362 II classe base usando vinculação dinâmica.
363 void virtualViaReference( const Employee &baseClassRef
364
365 baseClassRef.printO;
366 cout « “ recebeu $“ « baseClassRef.earnings
367 }
Boss: José Silva recebeu $800.00
Boss: José Silva recebeu $800.00
Boss: José Silva recebeu $800.00
Comissionado: Simone Bianchi recebeu $650.00
Comissionado: Simone Bianchi recebeu $650.00
Comissionado: Simone Bianchi recebeu $650.00
PieceWorker: Roberto Santos recebeu $500.00
PieceWorker: Roberto Santos recebeu $500.00
PieceWorker: Roberto Santos recebeu $500.00
HourlyWorker: Carmem Pereira recebeu $550.00
HourlyWorker: Carmem Pereira recebeu $550.00
HourlyWorker: Carmem Pereira recebeu $550.00
Fig. 10.1 Demonstrando o polimorfismo com a hierarquia de classes Employee -
figlO 01 . cpp (parte 2 de 2).
A linha 326
Boss b( “José, “Silva’, 800.00
instancia objeto b da classe derivada Boss e fornece os parâmetros ao
construtor, inclusive o primeiro nome, o último nome e o salário semanal fixo.
A linha 327
b.print // vinculação estática
invoca explicitamente a versão de Boss da função membro print, usando o
operador de seleção de membro ponto (.)para o objeto do tipo Boss específico b.
Este é um exemplo de vinculação estática, porque o tipo do objeto para o qual a
função está sendo chamada é conhecido durante a compilação. Esta chamada é
incluída para fins de comparação, para ilustrar que a função print correta é
invocada usando-se vinculação dinâmica.
A linha 328
cout « “ recebeu $“ « b.earningsQ; II vinculação estática
invoca explicitamente a versão de Boss da função membro earnings, usando o
operador de seleção de membro ponto ( . ) para o objeto do tipo Boss específico
b. Este também é um exemplo de vinculação estática. Esta chamada também é
incluída para fins de comparação, desta vez para ilustrar que a função earnings
correta é invocada usando-se ligação dinâmica.
A linha 329
virtualViaPointer( &b ); // usa vinculação dinâmica

CAPÍTULO lO - FUNÇÕES VIRTUAIS E POLIM0RFISMO 621


invoca a função virtualViaPointer (linha 335) com o endereço do objeto de classe
derivada b. A função recebe esse endereço em seu parâmetro baseClassPtr,
que é declarado como um const Employee . Isto é precisamente como realizar o
comportamento polimórfico.
A linha 357
baseClassPtr->print
invoca a função membro de impressão do objeto apontado por baseClassPtr.
Como print é declarada virtual na classe base, o sistema invoca a função de
impressão do objeto da classe derivada - o que é precisamente chamado de
comportamento polimórfico. Esta chamada de função é um exemplo de
vinculação dinâmica - a função virtual é invocada através de um ponteiro da
classe base, de modo que a decisão sobre que função invocar é adiada até o
momento da execução.
A linha 358
cout « ‘ recebeu $“ « baseClassPtr->earnings()
invoca a função membro earnings do objeto apontado por baseClassPtr. Como
earnings e declarada uma função virtual na classe base, o sistema invoca a
função earnings do objeto da classe derivada. Isso também é vinculação
dinâmica.
A linha 330
virtualViaReferencia (b); /1 usa vinculação dinâmica
invoca a função virtualViaReferencia (linha 363) para demonstrar que o
polimorfismo também pode ser realizado com funções virtual chamadas com
referências para a classe base. A função recebe o objeto b no parâmetro
baseClassRef que é declarado como um const Ernployee &. Esta é
precisamente a maneira de realizar comportamento polimórfico com referências.
A linha 365
baseClassRef print O;
invoca a função membro print do objeto referenciado por baseClassRef. Como
print é declarada uma função virtual na classe base, o sistema invoca a função
de impressão do objeto da classe derivada. Esta chamada de função é também
um exemplo de vinculação dinâmica - a função é invocada através de uma
referência para a classe base, de modo que a decisão sobre qual função invocar
é adiada até o tempo de execução.
A linha 366
cout « “ recebeu $‘ « baseClassRef.earnings()
invoca a função membro earnings do objeto referenciado por baseClassRef.
Como earnings é declarada uma função virtual na classe base, o sistema invoca
a função earnings do objeto da classe derivada. Isso também é ligação
dinâmica.
10.7 Novas classes e vinculação dinâmica
O polimorfismo e funções virtuais funcionam bem quando todas as classes
possíveis não são conhecidas com antecedência. Mas eles também funcionam
quando novos tipos de classes são acrescentados aos sistemas. Novas classes
são acomodadas pela vinculação dinâmica (também chamada de vincula ção
tardia). O tipo de um objeto não precisa ser conhecido durante a compilação
para que uma chamada de função virtual seja compilada. Durante a execução, a
chamada da função virtual é associada com a função membro apropriada do
objeto chamado.
Um programa gerenciador de tela pode agora exibir novos tipos de objetos, à
medida que são acrescentados ao sistema, sem o gerenciador de tela necessitar
ser recompilado. A chamada à função draw permanece a mesma. Os

CAPÍTULO 10 - FUNÇÕES VIRTUAIS E POLIMORFISMO 623


Observação de engenharia de software 10.8
_______ Uma classe pode herdar a inteiface e/ou a implementação de uma
classe base. As hierarquias projetadas para herança de implementação tendem
a ter sua funcionalidade colocada no alto da hierarquia - cada nova classe
derivada herda uma ou mais funções membro que foram definidas em uma
classe base, e a nova classe derivada usa as definições da classe base. As
hierarquias projetadas para herança de interface tendem a ter sua
funcionalidade colocada mais abaixo na hierarquia - uma classe base especifica
uma ou mais funções que deveriam ser definidas para cada classe na hierarquia
(i.e., elas têm a mesma assinatura), mas as classes derivadas individuais
fornecem suas próprias implementações da(s)função(ões).
1 II Fig. 10.2: shape.h
2 // Definição da classe base abstrata Shape
3 #ifndef SHAPEH
4 #define SHAPEH
5
6 class Shape
7 public:
8 virtual double area() const { return 0.0;
9 virtual double volume() const { return 0.0;
10
11 II funções virtuais puras sobrescritas nas classes derivadas
12 virtual void printShapeName() const 0;
13 virtual void print() const = 0;
14
15
16 #endif
Fig. 10.2 Demonstrando a herança de interface com a hierarquia de classes
Shape - shape . h.
17 II Fig. 10.2: pointl.h
18 /1 Definição da classe Point
19 #ifndef POINT1H
20 #define POINT1H
21
22 #include <iostreaxn>
23
24 using std::cout;
25
26 #include shape.h”
27
28 class Point : public Shape
29 public:
30 Point( int = 0, int = O ); II construtor default
31 void setpoint( int, int );
32 int getX() const { return x; }
33 int getY() const { return y; }
34 virtual void printShapeName() const { cout « “Point:
35 virtual void print() const;
36 private:
37 int x, y; II coordenadas x e y de Point
38
39
40 #endif
Fig. 10.2 Demonstrando a herança de intertace com a hierarquia de classes
Shape -pointi .h.

624 C++ COMO PROGRAMAR

41 // Fig. 10.2: pointl.cpp


42 1/ Definições de funções membro para a classe Point
43 #include “pointl.h”
44
45 Point::Point( int a, int b ) { setPoint( a, b );
46
47 void Point::setpoint( int a, int b
48
49 xa;
50 y=b;
51
52
53 void Point::print() const
54 { cout « ‘[‘ « x « ‘, « y « ‘]

A classe base Shape (linhas 1 a 16) consiste em quatro funções virtual public e
não contém quaisquer dados. As funções printShapeName e print são virtual
puras, assim elas são redefinidas em cada uma das classes derivadas. As
funções area e volume são definidas para retornar O. O. Essas funções são
redefinidas nas classe derivadas quando for apropriado para aquelas classes
terem um cálculo de área diferente e/ou um cálculo de volume diferente. Note
que Shape é uma classe abstrata e contém algumas funções virtual “impuras”
(area e volume). Classes abstratas também podem incluir funções e dados não-
virtuais, que serão herdados por classes derivadas.

56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

II Fig. 10.2: circlel.h


II Definição da classe Circle
#ifndef CIRCLE1H
#define CIRCLE1H
#include “pointi .
class Circle : public Point {
public:
// construtor default
Circle( double r = 0.0, int x = 0, int y = u );
void setRadius ( double );
double getRadius() const;
virtual double area() const;
virtual void printShapeName() const { cout virtual void print() const;
private:
double radius; // raio do círculo

« “Circle: “;

75 #endif
Fig. 10.2 Demonstrando a herança de interíace com a hierarquia de classes
Shape - circlel .h.

Fig. 10.2 Demonstrando a herança de interíace com a hierarquia de classes


Shape - circlel . cpp (partel de 2).

80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

95
Fig. 10.2 Demonstrando a herança de interface com a hierarquia de classes
Shape - pointi . cpp. 96
97

98
99
Fig.
(par

Ad

volu
simf
de fi

funç
obje
e fui

0.0

ame
clas
com

funç
uma

área
cIas
com
funç
altui

100
101
102
103

104
1OE
1OE
101

Fig.
(pai

7 II F 10.2 circlel.cpp
6 ig. :
7 // Definiçõe de funções membro para a classe
7 s Circie
7 #includ <iostream>
8 e
7
9

CAPÍTULO 10 - FUNÇÕES VIRTUAIS E P0LIM0RFIsM0 625


80 using std::cout;
81
82 #include “circlel.h”
83
84 Circle::Circle( double r, int a, int b
85 : Point( a, b ) II chama construtor da classe base
86 { setRadius( r ); }
87
88 void Circle::setRadius( double r ) { radius = r > O ? r : 0; }
89
90 double Circle::getRadius() const { return radius; }
91
92 double Circle::area() const
93 { return 3.14159 * radius * radius;
94
95 void Circle::print() const
96
97 Point: :printO;
98 cout « “; Raio = “ « radius;
99 1
Fig. 10.2 Demonstrando a herança de interface com a hierarquia de classes
Shape - circiel . cpp (parte 2 de 2).
A classe Point (linhas 17 a 54) é derivada de Shape por herança public. Um
Point tem uma área O . O e um volume 0. 0, assim as funções membros da
classe base area e volume não são sobrescritas aqui - elas são simplesmente
herdadas como definidas em Shape. As funções printShapeName e print são
implementações de funções virtual que foram definidas como virtual puras na
classe base - se não sobrescrevêssemos essas funções na classe Point, então
Point também seríamos uma classe abstrata e não seríamos capazes de
instanciar objetos Point. Outras funções membro incluem uma função set para
atribuir novas coordenadas x e y a um Point e função get para retornar as
coordenadas x e y de um Point.
A classe Circle (linhas 55 a 99) é derivada de Point por herança public. Um
círculo tem um volume O . O, assim a função membro volume da classe base
não é sobrescrita aqui - ela é herdada de Point, que previamente herdou volume
de Shape. Um círculo tem área não-nula, de modo que a função area é
sobrescrita nesta classe. As funções printShapeName e print são
implementações de funções virtual que foram definidas como virtual puras na
classe Shape. Se estas funções não são sobrescritas aqui, as versões de Point
destas funções serão herdadas. Outras funções membro incluem uma função
set para atribuir um novo raio a um círculo e uma função get para retornar o raio
de um Circle.
A classe Cylinder (linhas 100 a 154) é derivada de Circle por herança public. Um
Cylinder tem área e volume diferentes daqueles de um Circle, assim as funções
area e volume são ambas sobrescritas nesta classe. As funções
printShapeName e print são implementações de funções virtual que foram
definidas como virtual puras na classe Shape. Se estas funções não são
sobrescritas aqui, as versões de Circle destas funções serão herdadas. Outras
funções membro incluem funções set e get para atribuir uma nova altura e
retornar a altura de um Cylinder, respectivamente.

Fig. 10.2 Demonstrando a herança de interface com a hierarquia de classes


Shape - cylindrl.h
(parte 1 de 2).

10 II Fig. 10.2: cylindrl.h


0
10 // Definição da classe
1 Cylinder
10 #ifndef
2 CYLINDR1H
10 #define
3 CYLINDR1H
10 #include “circlel.h”
4
10
5
10 class Cylinder : Circl
6 public e{
10 public:
7

626 C++ CoMo PROGRAMAR


108 // construtor default 157 #in
109 Cylinder( double h = 0.0, double r = 0.0, 158
110 int x 0, int y = O ); 159 usi
111 160 usi
112 void setHeight( double ); 161
113 double getHeightO; 162 #ifl
114 virtual double area() const; 163
115 virtual double volume() const; 164 uSii
116 virtual void printShapeName() const { cout « “Cylinder: “; } 165 usii
117 virtual void print() const; 166 usi
118 private: 167
119 double height; II altura do cilindro 168 #ifl
120 }; 169 #in
121 170 #in
122 #endif 171 #in
172
Fig. 10.2 Demonstrando a herança de inter-face com a hierarquia de classes
Shape - cylindrl.h 173 voi
(parte 2 de 2). 174 voi
175
176 int
123 II Fig. 10.2: cylindrl.cpp 177
124 // Definições de funções membro e friend para a classe Cylinder 178
125 #include <iostream> 179
126 180
127 using std: :cout; 181
128 182
129 #include ‘cylindrl.h” 183
130 184
131 Cylinder::Cylinder( double h, double r, int x, int y ) 185
132 : Circle( r, x, y ) // chama construtor da classe base 186
133 { setHeight( h ) } 187
134 188
135 void Cylinder: :setHeight( double h ) 189
136 { height = h > O ? h : 0; } 190
137 191
138 double Cylinder: :getHeight() { return height; } 192
139 193
140 double Cylinder::area() const 194
141 { 195
142 // área da superfície do cilindro 196
143 return 2 * Circle::area() ÷ 197
144 2 * 3.14159 * getRadius() * height; 198
145 } 199
146 200
147 double Cylinder::volume() const 201
148 { return Circle::area() * height; } 202
149 203
150 void Cylinder: :print() const 204
151 205
152 Circle::printQ; 206
153 cout « Altura = « height; 207
154 } 208
Fig. 10.2 Demonstrando a herança de interface com a hierarquia de classes
Shape - cylindrl - cpp. 209
211
155 // Fig. 10.2: figlOO2.cpp 212
156 // Programa de teste para a hierarquia forma, ponto, círculo, cilindro
Fig. 10.2 Demonstrando a herança de interface com a hierarquia de classes
Shape - figlO 02. cpp Fig. 10.2
(parte 1 de 3). (parte 2 c

CAPÍTULO 10- FUNÇÕES VIRTUAIS E POLIMORFISMO 627


157 #include <iostreain>
158
159 using std: :cout;
160 using std: :endl;
161
162 #include <iomanip>
163
164 using std::ios;
165 using std: :setiosflags;
166 using std::setprecision;
167
168 #include ‘shape.h”
169 #include “pointl.h”
170 #include “circlel.h”
171 #iriclude “cylindrl.h”
172
173 void virtualViaPointer( const Shape *
174 void virtualViaReference( const Shape & );
175
176 int main()
177 {
178 cout « setiosflags( ios::fixed ios::showpoint
179 « setprecision( 2 );
180
181 Point point( 7, 11 ); II cria um Point
182 Circie circle( 3.5, 22, 8 ); // cria um Circie
183 Cylinder cylinder( 10, 3.3, 10, 10 ); II cria uni Cylmnder
184
185 point.printShapeNameO; II vinculação estática
186 point.printO; II vinculação estática
187 cout « ‘\n’;
188
189 circle.printShapeNameO; // vinculação estática
190 circle.printO; II vinculação estática
191 cout « ‘\n’;
192
193 cy1inder.printShapet’Iaxne 1/ vinculação estática
194 cylinder.printO; li vinculação estática
195 cout « “\n\n”;
196
197 Shape *arrayofshapes[ 3 ]; II array de ponteiros para classe base
198
199 li aponta arrayOfShapes[03 para objeto Point da classe derivada
200 arrayOfShapes[ O J = &point;
201
202 // aponta arrayOfShapes[1) para objeto Circle da classe derivada
203 arrayOfShapes[ 1 ) = &circle;
204
205 // aponta arrayOfShapes[2J para objeto Cylinder da classe derivada
206 arrayOfShapes[ 2 ] = &cylinder;
207
208 // Itera ao longo de arrayOfShapes e chama virtualViaPointer
209 II para imprimir nome, atributos, área e volume da forma
210 // de cada objeto usando vinculação dinâmica.
211 cout « “Chamadas de função virtuais feitas com
212 « ‘ponteiros para a classe base\n”;
213
214 for ( int i = 0; i < 3; i++
Fig. 10.2 Demonstrando a herança de intertace com a hierarquia de coasses
Shape - figl0_02 . cpp (parte 2 de 3).

628 C++ COMO PROGRAMAR


215 virtualViaPointer( arrayOfShapes( i 1
216
217 // Itera ao longo de arrayOfShapes e chama virtualViaReference
218 II para imprimir nome, atributos, área e volume da forma
219 // de cada objeto usando vinculação dinâmica.
220 cout « ‘Chamadas de função virtuais feitas com
221 « “referências para a classe base\n’;
222
223 for ( int j 0; j < 3; j++
224 virtualViaReference( *arrayofShapes[ j ] );
225
226 return O;
227
228
229 /1 Faz chamadas de função virtuais com um ponteiro
230 // para a classe base usando vinculação dinâmica.
231 void virtualViaPointer( const Shape *baseClassptr
232 {
233 baseClassPtr->printShapeNameO;
234 baseClassPtr->print
235 cout « “\nÁrea = “ « baseClassPtr->area()
236 « “\nVolume “ « baseClassPtr->volume() « “\n\n”;
237
238
239 II Faz chamadas de função virtuais com uma referência
240 II para a classe base usando vinculação dinâmica.
241 void virtualViaReference( const Shape &baseClassRef
242 {
243 baseClassRef.printShapeNameO;
244 baseClassRef.print
245 cout « ‘\nÁrea = “ « baseClassRef.area()
246 « “\nVolume = “ « baseClassRef.volume() « “\n\n”;
247 }
Point: [7, 11)
Circle: [22, 8]; Raio = 3.50
Cylinder: [10, 10); Raio = 3.30; Altura = 10.00
Chamadas de função virtuais feitas com ponteiros para a classe base
Point: (7, 11]
Área = 0.00
Volume = 0.00
Circle: [22, 8]; Raio 3.50
Área = 38.48
Volume = 0.00
Cylinder: [10, 10]; Raio = 3.30; Altura 10.00
Área = 275.77
Volume = 342.12
Chamadas de função virtuais feitas com referências para a classe base
Point: (7, 11]
Área = 0.00
Volume = 0.00
Círcle: [22, 8]; Raio = 3.50
Área = 38.48
Volume 0.00
Cylinder: (10, 10]; Raio = 3.30; Altura = 10.00
Área = 275.77
Volume = 342.12

Fig. 10.2 Demonstrando a herança de interface com a hierarquia de classes


Shape - figl0_02 . cpp (parte 3 de 3).

CAPÍTULO lO - FUNÇÕES VIRTUAIS E POLIMORFISMO 629


O programa de teste (linhas 155 a 247) começa instanciando o objeto point do
tipo Point, o objeto circie do tipo Circle e o objeto cylinder do tipo Cylinder. As
funções printShapeName e print são invocadas para cada um dos objetos, para
imprimir o nome do objeto e para ilustrar que os objetos são corretamente
inicializados. Cada chamada printShapeName e print, nas linhas 185 a 194, usa
vinculação estática
- o compilador sabe durante a compilação o tipo de cada objeto para o qual
printShapeName e print são chamadas.
A seguir, o array arrayofShapes, no qual cada elemento é do tipo Shape , é
declarado. Este array de ponteiros da classe base é usado para apontar para
cada um dos objetos das classes derivadas. O endereço do objeto point é
atribuído a arrayOfShapes [ O 1 (linha 200), o endereço do objeto circle é
atribuído a arrayOfShapes [ 1 1 (linha 203) e o endereço do objeto cylinder é
atribuído a arrayOfShapes [ 2 1 (linha 206).
Em seguida, uma estrutura for (linha 214) percorre o array arrayOfShapes e
invoca a função
virtualViaPointer (linha 215)
virtualViaPointer ( arrayOfShapes [ i ] );
para cada elemento do array. A função virtualViaPointer recebe no parâmetro
baseClassPtr (do tipo const Shape *) o endereço armazenado em um elemento
do arrayOfShapes. Toda vez que virtualViaPointer é executada, as quatro
chamadas de função virtual seguintes são feitas
baseClassPtr->printShapeName O
baseClassPtr->print()
baseClassPtr->area ()
baseClassPtr->volume O
Cada uma destas chamadas invoca uma função virtual sobre o objeto que
baseClassPtr aponta durante a execução - um objeto cujo tipo não pode ser
determinado durante a compilação. A saída ilustra que as funções apropriadas
para cada uma das classes são invocadas. Primeiro, o string “Point: “ e as
coordenadas do objeto point são mostradas; a área e o volume são ambos 0.00.
Em seguida, o string “Circle:”, as coordenadas do centro do objeto circle e o raio
do objeto circle são mostrados; a área de circle é calculada e o volume é
retornado como O . 00. Finalmente, o string “Cylinder: as coordenadas do centro
da base do objeto cylirider, o raio do objeto cylinder e a altura do objeto cylinder
são mostrados; a área e o volume do cilindro são calculados. Todas as
chamadas das funções virtuais printShapeName, print, area e volume são
resolvidas durante a execução com vinculação dinâmica.
Finalmente, uma estrutura for (linha 223) percorre o arrayOfShapes e invoca a
função
virtualViaReference (linha 224)
virtualViaReference ( *arrayofshapes [ j 1 );
para cada um dos elementos do array. A função virtualViaReference recebe em
seu parâmetro baseClassRef (do tipo const Shape &), uma referência formada
pela desreferenciação do endereço armazenado em um elemento do array.
Durante cada chamada a virtualViaReference, são feitas as seguintes chamadas
a funções virtuais
baseClassRef .printShapeName O
baseClassRef .print()
baseCiassRef. area O
baseClassRef .volume O
Cada uma das chamadas precedentes invoca estas funções sobre o objeto que
baseClassRef referencia. A
saída produzida usando referências para a classe base é idêntica à saída
produzida usando-se ponteiros para a
classe base.

630 C++ COMO PROGRAMAR


10.10 Polimorfismo, funções virtual e vinculação dinâmica “vistos por dentro”
C++ facilita a programação do polimorfismo. Certamente, é possível se
programar com polimorfismo em linguagens não-orientadas a objetos, tal como
C, mas fazê-lo exige manipulações de ponteiros complexas e potencialmente
perigosas. Nessa seção, discutimos como C++ implementa o polimorfismo,
funções virtuais e a vinculação dinâmica internamente. Isso lhe dará uma sólida
compreensão de como realmente funcionam estes recursos. Mais importante
ainda, ajudará a avalíar o overhead provocado pelo polimortismo - em consumo
de memória e tempo de processador adicionais. Isso ajudará a decidir quando
usar polimorfismo e quando evitá-lo. Como você verá no Capítulo 20, “A
biblioteca padrão de gabaritos (STL)”, os componentes da STL foram
implementados sem usar polimorfismo e funções virtuais - isto foi feito para
evitar overhead durante a execução e obter um desempenho ótimo para atender
aos requisitos especiais da STL.
Primeiro, explicaremos as estruturas de dados que o compilador C++ constrói
durante a compilação para
suportar o polimorfismo durante a execução. Então, mostraremos como um
programa em execução usa estas estruturas de dados para executar funções
virtual e obter a vinculação dinâmica associada com o polimorfismo.
Quando C++ compila uma classe que tem uma ou mais funções virtual, o
compilador constrói uma tabela de funções virtuais (vtable) para aquela classe. A
vtable é usada pelo programa em execução para selecionar as implementações
apropriadas da função toda vez que uma função virtual daquela classe é
executada. A Fig. 10.3 ilustra as tabelas de funções virtual para as classes
Shape. Point, Circle e Cylinder.
Na vtable para a classe Shape, o primeiro ponteiro de função aponta para a
implementação da função area
para aquela classe, isto é, uma função que retorna uma área de valor O . O. O
segundo ponteiro de função aponta para
a função volume, que também retorna O . O. As funções printShapeName e print
são ambas virtual puras
- elas não têm implementações, de modo que seus ponteiros de função são
ambos inicializados com O. Qualquer
classe que tenha um ou mais ponteiros O em sua vtable é uma classe abstrata.
Classes sem quaisquer ponteiros O na
vtable (como Point, Circle e Cylinder) são classes concretas.
A classe Point herda as funções area e volume da classe Shape. de modo que o
compilador simplesmente deixa esses dois ponteiros na vtable para a classe
Point como cópias dos ponteiros de area e volume da classe Shape. A classe
Point redefine a função printShapeNaine para imprimir Point:”. por isso o
ponteiro de função aponta para a função printShapeNaxne da classe Point. Point
também sobrescreve print. de modo que o ponteiro de função correspondente
aponta para a função da classe Point que imprime [x, y].
O ponteiro de função para area de Circle, na vtable para a classe Circle, aponta
para a função area de Circie que retorna tr2. O ponteiro da função volume
simplesmente é copiado da classe Point - aquele ponteiro foi previamente
copiado de Shape para Point. O ponteiro da função printShapeName aponta
para a versão de Circle da função que imprime “Circie: “. O ponteiro da função
print aponta para a função print de Circle que imprime [x, y] r.
O ponteiro da função area de Cylinder, na vtable para a classe Cylinder. aponta
para a função area de Cylinder que calcula a área da superfície do Cylinder. isto
é 27tr2 + 2trh. O ponteiro da função volume de Cylinder aponta para uma função
volume que retorna 2Ttr2h. O ponteiro da função printShapeNante de Cylinder
aponta para uma função que imprime “Cylinder:’. O ponteiro da função print de
Cylinder aponta para sua função que imprime [ x, y 1 r h.
O polimorfismo é realizado através de uma estrutura de dados complexa, que
envolve três níveis de ponteiros. Discutimos só um nível - os ponteiros de função
na víable. Estes ponteiros apontam para as funções reais que devem ser
executadas quando é invocada uma função virtual.
Agora, consideraremos o segundo nível de ponteiros. Sempre que um objeto de
uma classe com funções
virtual é instanciado, o compilador anexa à frente do objeto um ponteiro para a
vtable daquela classe. [Nota:
normalmente, este ponteiro está na frente do objeto, mas não é obrigatório que
seja implementado deste modo.j
O terceiro nível de ponteiros é simplesmente o handle para o objeto que está
recebendo a chamada da função virtual (este handie também pode ser uma
referência).
Agora, vejamos como uma chamada de função virtual típica é executada.
Considere a chamada
baseClassPtr->printShapeName O
na função virtualViaPointer. Suponha, para a discussão seguinte, que
baseClassPtr contém o endereço que está em arrayOfShapes [ 1 1 (i.e., o
endereço do objeto circle). Quando o compilador compila este

CAPÍTULO 10 - FUNÇÕES VIRTUAIS E POLIMORFISMO 631

O fluxo de uma chamada de função virtual baseClassPtr->printShapeName O;


é ilustrado pelas flechas em negrito acima.

passa &circle para baseClassPtr obtém o objeto Circle


obtém a vtable de Circie
obtém o ponteiro de printShapeName na vtable executa printShapeName para
Circie

Fig. 10.3 Fluxo de controle de uma chamada de função virtual.

comando, ele determina que a chamada está sendo de fato feita a partir de um
ponteiro para a classe base e que printShapeName é uma função virtual.
Em seguida, o compilador determina que printShapeName é a terceira entrada
em cada uma das vtables.
Para localizar esta entrada, o compilador nota que ele necessitará saltar as
primeiras duas entradas. Deste modo, o compilador compila um offset ou
deslocamento de 8 bytes (4 bytes para cada ponteiro em máquinas de 32 bits,

vtable de Shape

height 10.00

Legenda
a funçãoarea
v = função volume
psn função printShapeName
pr = função print
entrada com o significa função virtual pura
r = raio: h = altura

632 C++ COMO PROGRAMAR


populares hoje em dia) para o código objeto em linguagem de máquina que
executará a chamada da função virtual.
Então, o compilador gera o código que irá (Nota: os números na lista abaixo
correspondem aos números nos
círculos na Fig. 10.3):
1. Selecionar a i-ésima entrada de arrayOfShapes (neste caso, o endereço do
objeto circle) e passálo para virtualViaPointer. Isso inicializa baseClassPtr para
apontar para circle.
2. Derreferenciar aquele ponteiro para obter o objeto circle - que, como você se
lembra, começa com um ponteiro para a vtable de Circle.
3. Derreferenciar o ponteiro para a vtable de circie para chegar à vtable de
Circle.
4. Saltar o deslocamento de 8 bytes para pegar o ponteiro da função
printShapeName.
5. Derreferenciar o ponteiro da função printShapeNaine para formar o nome da
verdadeira função a ser executada e usar o operador de chamada de função ()
para executar a função printShapeName apropriada e imprimir o string de
caracteres “Circle:”.
As estruturas de dados da Fig. 10.3 podem parecer complexas, mas a maior
parte dessa complexidade é administrada pelo compilador e escondida do
programador, simplificando a programação polimórfica em C++.
As operações de derreferenciamento de ponteiros e acessos à memória que
ocorrem em toda chamada de
função virtual exigem algum tempo de execução adicional. As vtables e os
ponteiros de vtable acrescentados aos objetos exigem alguma memória
adicional.
Esperamos que você, agora, tenha informações suficientes sobre como operam
as funções virtual para
determinar se o seu uso é apropriado para cada aplicação que você estiver
pensando em fazer.
Dica de desempenho 10.1
f O polimoifismo implementado com funções virtual e vincula ção dinâmica é
eficiente. Os pro gramadores podem usar estes recursos com pequeno impacto
sobre o desempenho do sistema.
Dica de desempenho 10.2
_____ As funções virtuais e a vincula ção dinâmica possibilitam a programação
polimóifica, substituindo a lógica de programação com switchs. Os compiladores
otimizadores de C++ normalmente geram código que executa pelo menos tão
eficazmente quanto a lógica de programação baseada em swi tchs codificada
pelo programador De uma forma ou de outra, o overhead do polimoifismo é
aceitável para a maioria das aplicações. Mas, em algumas situações - por
exemplo, aplicativos de tempo real com requisitos de desempenho especiais - o
overhead do polimorfismo pode ser muito elevado.
Resumo
Com funções virtual e polimorfismo, torna-se possível projetar e implementar
sistemas que são mais facilmente extensíveis, Os programas podem ser escritos
para processar objetos de tipos que podem ainda não existir quando o programa
está em desenvolvimento.
• A programação polimórfica com funções virtual pode eliminar a necessidade de
lógica de programação que emprega switchs. O programador pode usar o
mecanismo das funções virtuais para executar uma lógica equivalente de forma
automática, evitando deste modo os tipos de erros tipicamente associados com
a lógica de programação que emprega switchs. Um código cliente que toma
decisões sobre tipos e representações de objetos indica um mau projeto de
classe.
• Classes derivadas podem fornecer suas próprias implementações de uma
função virtual de uma classe base se necessário, mas, se elas não o fizerem, é
usada a implementação da classe base.
• Se uma função virtual for chamada referenciando-se um objeto específico por
nome e usando-se o operador de seleção de membro ponto, a referência é
resolvida durante a compilação (isto é chamado de vincula ção estática) e a
função virtual que é chamada é aquela definida para a (ou herdada pela) classe
daquele de objeto em particular.

CAPÍTULO 10 - FUNÇÕES VIRTUAIS E POLIMORFISMO 633


Existem muitas situações em que é útil se definir classes das quais o
programador nunca pretende instanciar quaisquer objetos. Tais classes são
chamadas de classes abstratas. Como são usadas só como classes base,
referir-nos-emos a elas como
classes base abstratas. Nenhum objeto do tipo de uma classe abstrata pode ser
instanciado em um programa.
)S nos • As classes das quais podemos instanciar objetos são chamadas de
classes concretas.
• Uma classe é tornada abstrata declarando-se uma ou mais de suas funções
como virtual puras. Uma função virtual
)assá- pura é uma função com um inicializador = O em sua declaração.
• Se uma classe é derivada de uma classe com uma função virtual pura e não se
fornece uma definição para aquela função
rn um virtual pura na classe derivada, então aquela função virtual permanece
pura na classe derivada. Conseqüentemente, a
classe derivada também é uma classe abstrata.
• C++ possibilita o polimorfismo - a capacidade de objetos de classes diferentes
relacionadas por herança responderem dife rentement à mesma chamada de
função membro.
• O polimorfismo é implementado através de funções virtual.
a ser • Quando é feita uma solicitação para usar uma função virtual, através de
um ponteiro ou referência da classe base, C++
apro- escolhe a função sobrescrita correta, na classe derivada apropriada
associada com o objeto.
• Através do uso de funções virtual e polimorfismo, uma chamada de função
membro pode produzir ações diferentes,
trada dependendo do tipo do objeto que está recebendo a chamada.
• Embora não possamos instanciar objetos de classes base abstratas, podemos
declarar ponteiros para classes base abstratas.
la de Tais ponteiros podem ser usados para possibilitar manipulações
polimórficas de objetos de classes derivadas quando tais
s aos objetos forem instanciados de classes concretas.
nara • Novos tipos de classes são regularmente acrescentados a sistemas
existentes. Novas classes são acomodadas por vinculação dinâmica (também
chamada de vinculação tardia), O tipo de um objeto não precisa ser conhecido
durante a compilação para
que uma chamada de função virtual seja compilada. Durante a execução, a
chamada da função virtual é associada com a função membro do objeto
receptor.
• A vinculação dinâmica possibilita aos vendedores de software independentes
(lSVs) distribuir seu software sem revelar
segredos de sua propriedade. As distribuições de software podem consistir
somente de arquivos em cabeçalho e arquivos de
código-objeto. Nenhum código-fonte precisa ser revelado, Os desenvolvedores
de software podem então usar a herança para
derivar novas classes daquelas fornecidas pelos ISVs. O software que trabalha
com as classes fornecidas pelos ISVs continu ar a funcionar com as classes
delas derivadas e usará (através da vinculação dinâmica) funções virtual
sobrescritas
- fornecidas nestas classes.
lógi dig
• A vinculação dinâmica exige que, durante a execução, a chamada de uma
função membro virtual seja redirecionada para
fica- a versão da função virtual apropriada para a classe. Uma tabela de funções
virtual chamada vtable é implementada
oria como um array contendo ponteiros para funções. Cada classe que contém
funções virtual tem uma vtable. Para cada
função virtual na classe, a vtable tem uma entrada contendo um ponteiro de
função para a versão da função virtual a
s L4e ser usada para um objeto daquela classe. A função virtual a ser usada
para uma classe particular poderia ser a função
definida naquela classe, ou ela poderia ser uma função herdada direta ou
indiretamente de uma classe base mais alta na
hierarquia.
• Quando uma classe base fornece uma função membro virtual, as classes
derivadas podem sobrescrever a função virtual. mas elas não precisam,
obrigatoriamente, sobrescrevê-la. Desse modo, uma classe derivada pode usar
a versão de uma classe base de uma função membro virtual, e isto seria
indicado na vtable.
ensI est
• Cada objeto de uma classe com funções virtual contém um ponteiro para a
vtable daquela classe. O ponteiro da função apropriada na s’table é obtido e
derreferenciado para completar a chamada durante a execução. Esta pesquisa
na vtable e derreferenciamento do ponteiro causam um certo overhead durante
a execução, normalmente menor do que o melhor código
rega cliente possível.
rma
• Declare o destruidor da classe base como virtual se a classe contém funções
virtuais. Isto torna virtual todos os
destruidores das classes derivadas, embora não tenham o mesmo nome que o
destruidor da classe base. Se um objeto na
hierarquia é explicitamente destruído aplicando-se o operador delete a um
ponteiro da classe base para um objeto de uma
classe derivada, é chamado o destruidor para a classe apropriada.
Qualquer classe que tenha um ou mais ponteiros O em sua vtable é uma classe
abstrata. Classes sem quaisquer ponteiros O na
ode vtable (como Point, Circle e Cylinder) são classes concretas.
ual

634 C++ COMO PROGRAMAR

Terminologia

classe abstrata classe base abstrata

classe base direta


classe base indireta
classe concreta
classe derivada
construtor de classe derivada
conversão explícita de ponteiro
converter um ponteiro de classe derivada para ponteiro de classe base
deslocamento na vtable

destruidor virtual eliminando comandos switch extensibilidade


função virtual
função virtual da classe base

função virtual pura (=0) herança


herança de implementação herança de interface hierarquia de classes

lógica de switch

Erros comuns de programação

offset na vtable
polimorfismo
ponteiro da vtable
ponteiro para uma classe abstrata ponteiro para uma classe base ponteiro para
uma classe derivada programação “específica” programação “genérica”
referência para uma classe abstrata referência para uma classe base referência
para uma classe derivada reutilização de software sobrescrever uma função
virtual sobrescrever uma função virtual pura tabela de funções virtual vendedor
de software independente (ISV) vinculação antecipada vinculação dinâmica
vinculação estática vinculação tardia
vtable

10. 1 Tentar instanciar um objeto de uma classe abstrata (i.e., uma classe que
contém uma ou mais funções virtual puras) é um erro de sintaxe.
10.2 Construtores não podem ser virtual. Declarar um construtor como virtual é
um erro de sintaxe.

Boas práticas de programação

10.1 Embora certas funções sejam implicitamente virtual, por causa de uma
declaração feita mais acima na hierarquia de classes, declarar explicitamente
estas funções como virtual em cada nível da hierarquia melhora a clareza do
progra ma.
10.2 Se uma classe tem funções virtuais, forneça um destruidor virtual, ainda
que não seja necessário um para a classe. As classes derivadas desta classe
podem conter destruidores que devem ser chamados corretamente.

Dicas de desempenho

10.1 O polimorfismo implementado com funções virtual e vinculação dinâmica é


eficiente. Os programadores podem usar estes recursos com pequeno impacto
sobre o desempenho do sistema.
10.2 As funções virtuais e a vinculação dinâmica possibilitam a programação
polimórfica, substituindo a lógica de programação com switchs. Os compiladores
otimizadores de C++ normalmente geram código que executa pelo menos tão
eficazmente quanto a lógica de programação baseada em switchs codificada
pelo programador. De uma forma ou de outra, o overhead do polimorfismo é
aceitável para a maioria das aplicações. Mas, em algumas situações - por
exemplo, aplicativos de tempo real com requisitos de desempenho especiais - o
overhead do polimorfismo pode ser muito elevado.

Observações de engenharia de software

10.1 Uma conseqüência interessante do uso de funções virtual e polimorfismo é


que os programas adquirem uma aparência mais simples. Eles contêm menos
ramificações lógicas, favorecendo o emprego de código seqüencial mais
simples.
Isso facilita o teste, a depuração e a manutenção de programas e evita erros.
CAPÍTULO 10- FUNÇÕES VIRTUAIS E P0LIM0RFISM0 635
10.2 Uma vez que uma função seja declarada virtual, ela permanece virtual por
toda a hierarquia de herança, daquele ponto para baixo, mesmo se não é
declarada virtual quando uma classe a sobrescreve.
10.3 Quando uma classe derivada escolhe não definir uma função como virtual,
a classe derivada simplesmente herda a definição da função virtual da sua
classe básica imediata.
10.4 Se uma classe é derivada de uma classe com uma função virtual pura e se
nenhuma definição for fornecida para aquela função virtual pura na classe
derivada, então aquela função virtual permanece pura na classe derivada.
Conseqüentemente, a classe derivada também é uma classe abstrata.
10.5 Com funções virtual e polimorfismo, o programador pode tratar com
generalidades e deixar para o ambiente de execução a preocupação com os
aspectos específicos. O programador pode fazer com que uma grande
variedade de
objetos se comporte de modos apropriados para aqueles objetos, sem sequer
conhecer os tipos dos mesmos.
10.6 O polimorfismo promove a extensibilidade: o software escrito para invocar o
comportamento polimórfico é escrito independentemente dos tipos dos objetos
para os quais as mensagens são enviadas. Deste modo, novos tipos de objetos
que podem responder a mensagens existentes podem ser acrescentados a um
sistema como esse, sem modificar o sistema básico. Com a exceção do código
cliente que instancia novos objetos, os programas não necessitam ser
recompilados.
10.7 Uma classe abstrata define uma interface para os vários membros de uma
hierarquia de classes. A classe abstrata contém funções virtuais puras que serão
definidas nas classes derivadas. Todas as funções na hierarquia podem usar
essa mesma
interface, através do polimorfismo.
10.8 Uma classe pode herdar a interface e/ou a implementação de uma classe
base. As hierarquias projetadas para herança de
implementação tendem a ter sua funcionalidade colocada no alto da hierarquia -
cada nova classe derivada herda uma ou
mais funções membro que foram definidas em uma classe base, e a nova classe
derivada usa as definições da classe base.
As hierarquias projetadas para herança de interface tendem a ter sua
funcionalidade colocada mais abaixo na hierarquia
- uma classe base especifica uma ou mais funções que deveriam ser definidas
para cada classe na hierarquia (i.e., elas têm
a mesma assinatura), mas as classes derivadas individuais fornecem suas
próprias implementações da(s) função(ões).
Exercício de auto-revisão
10.1 Preencha os espaços em branco em cada um dos seguintes itens:
a) Usar herança e polimorfismo ajuda a eliminar a lógica de _______________
b) Uma função virtual pura é especificada colocando-se no fim de seu protótipo,
na definição da classe.
e) Se uma classe contém uma ou mais funções virtual puras, é uma
______________
d) Uma chamada de função resolvida durante a compilação é chamada de
vinculação
e) Uma chamada de função resolvida durante a execução é chamada de
vinculação
Respostas ao exercício de auto-revisão
10.1 a) switch. b) =0. c) classe base abstrata. d) estática ou antecipada. e)
dinâmica ou tardia.
Exercícios
10.2 O que são funções virtual? Descreva uma circunstância em que funções
virtual seriam apropriadas.
10.3 Dado que construtores não podem ser virtual, descreva um esquema com o
qual você pode obter um efeito semelhante.
10.4 Como é que o polimorfismo possibilita a você programar “no geral” ao invés
de “no específico”. Discuta as vantagens chaves da programação “no geral”.
10.5 Discuta os problemas de programação com lógica que emprega switchs.
Explique por que o polimorfismo é uma alternativa efetiva ao uso de lógica de
programação que emprega switchs.
10.6 Distinga entre vinculação estática e vinculação dinâmica. Explique o uso de
funções virtual e da vtable na vinculação dinâmica.
10.7 Distinga entre herdar a interface e herdar a implementação. Como
hierarquias de herança projetadas para herdar a interface diferem daquelas
projetadas para herdar a implementação?
10.8 Distinga entre funções virtual e funções virtual puras.
10.9 (Verdadeiro/Falso) Todas as funções virtual em uma classe base abstrata
devem ser declaradas como funções virtual puras.

636 C++ COMO PROGRAMAR


10.10 Sugira um ou mais níveis de classes base abstratas para a hierarquia de
Shape discutida neste capítulo (o primeiro nível é Shape e o segundo nível
consiste nas classes TwoDimerisionalShape e ThreeDimensionalShape).
10.11 Como o polimorfismo promove a extensibilidade?
10.12 Foi solicitado a você desenvolver um simulador de vôo que terá saídas
elaboradas. Explique por que a programação polimórfica seria especialmente
efetiva para um problema desta natureza.
10.13 Desenvolva um pacote básico para gráficos. Use a hierarquia de classes
por herança de Shape do Capítulo 9. Limite-se a formas de duas dimensões, tais
como quadrados, retângulos, triângulos e círculos. Interaja com o usuário. Deixe
o usuário especificar a posição, o tamanho, a forma e os caracteres de
preenchimento a serem usados para desenhar cada forma, O usuário pode
especificar muitos itens da mesma forma. A medida que você cria cada forma,
coloque um ponteiro Shape* para cada novo objeto do tipo Shape em um array.
Cada classe tem sua própria função membro draw. Escreva um gerenciador de
tela polimórfico que percorre o array (usando de preferência um iterador)
enviando mensagens draw para cada objeto do array para formar uma imagem
de tela. Redesenhe a imagem na tela toda vez que o usuário especifica uma
forma adicional.
10.14 Modifique o sistema de folha de pagamento da Fig. 10.1 para acrescentar
os membros de dados privados dataDeNascímento (um objeto do tipo Date) e
codigoDeDeparta!nento (um int) à classe Employee. Assuma que essa folha de
pagamento é processada uma vez por mês. Então, à medida que seu programa
calcula a folha de pagamento para cada Employee (polimorficamente), some
uma gratificação de $100.00 à quantia da folha de pagamento da pessoa se este
for o mês de aniversário do Employee.
10.15 No Exercício 9.14, você desenvolveu uma hierarquia de classes Shape e
definiu as classes na hierarquia. Modifique a hierarquia de forma que a classe
Shape seja uma classe base abstrata contendo a interface da hierarquia. Derive
TwoDimensionalShape e ThreeDimensionalShape a partir da classe Shape -
estas classes deveriam também ser abstratas. Use uma função virtual print para
dar saída ao tipo e dimensão de cada classe. Também inclua funções virtual
area e volume, de forma que estes cálculos possam seqr executados para
objetos de cada classe concreta na hierarquia. Escreva um programa de teste
que testa a hierarquia de classes de Shape.