P. 1
13 - Capítulo 10 - Funções Virtuais e Polimorfismo

13 - Capítulo 10 - Funções Virtuais e Polimorfismo

|Views: 1.171|Likes:
Publicado porbatokas

More info:

Published by: batokas on Apr 01, 2009
Direitos Autorais:Attribution Non-commercial

Availability:

Read on Scribd mobile: iPhone, iPad and Android.
download as RTF, PDF, TXT or read online from Scribd
See more
See less

05/23/2013

pdf

text

original

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; A 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ãovirtuais, 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 6 7 7 7 8 7 9 II F 10.2 circlel.cpp ig. : // Definiçõe de funções membro para a classe s Circie #includ <iostream> e

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 0 10 1 10 2 10 3 10 4 II Fig. 10.2: cylindrl.h // Definição da classe Cylinder #ifndef CYLINDR1H #define CYLINDR1H #include “circlel.h”

10 5 10 class Cylinder : 6 public 10 public: 7

Circl e{

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.

You're Reading a Free Preview

Descarregar
scribd
/*********** DO NOT ALTER ANYTHING BELOW THIS LINE ! ************/ var s_code=s.t();if(s_code)document.write(s_code)//-->