| Entendendo C++ |
| Funções virtuais |
| Ao longo desses tutoriais nós vimos vários exemplos de
herança, porque herança é muito importante para programação orientada a objeto. Vimos
que herança permite que dados membro e que funções membro sejam acrescentadas às
funções derivadas. Vimos também vários exemplos em que o mecanismo de herança foi
usado para modificar o comportamento de uma função. Por exemplo, no Tutorial 3 nós
vimos um exemplo onde a função
Insert da classe base List foi sobreposta por uma
outra função Insert que continha totalização. Uma hierarquia similar é mostrada a seguir, usando uma classe base denominada List e uma classe derivada denominada TotalingList: |
| #include <iostream.h> class List { int array[100]; int count; public: List(): count(0) {} void Insert(int n) { array[count++]=n; } int Get(int i) { return array[i]; } int Size() { return count; } }; void ManipList(List list) { // do things to the list list.Insert(100); list.Insert(200); // do things to the list } class TotalingList: public List { int total; public: TotalingList(): List(), total(0) {} void Insert(int n) { total += n; List::Insert(n); } int GetTotal() { return total; } }; void main() { TotalingList list; int x; list.Insert(10); list.Insert(5); cout << list.GetTotal() << endl; ManipList(list); cout << list.GetTotal() << endl; for (x=0; x < list.Size(); x++) cout << list.Get(x) << ' '; cout << endl; } |
| Nesse código, a classe List implementa a lista mais
simples possível com as três funções membro Insert, Get e Size e o
construtor. A função ManipList é um exemplo de uma função qualquer que usa a classe List, e
que chama a função Insert duas vezes apenas como ilustração do que pretendemos demonstrar. A classe TotalingList herda a classe List e acrescenta um dado membro denominado total. Esse dado membro armazena o total corrente de todos os números contidos na lista. A função Insert é sobreposta para que total seja atualizado a cada inserção. A função main declara uma instância da classe TotalingList. Insere os valores 10 e 5 e imprime o total. Em seguida chama ManipList. Pode ser uma surpresa para você que essa construção não resulte em erro de compilação: se você olhar o protótipo de ManipList vai ver que ela espera uma parâmetro do tipo List, e não do tipo TotalingList. Acontece que C++ entende certas peculiaridades sobre herança, uma delas é que um parâmetro do tipo classe base deve aceitar também qualquer tipo classe derivada daquela classe base. Portanto, desde que TotalingList é derivada da classe List, ManipList vai aceitar um parâmetro do tipo TotalingList. Esse é um dos recursos do C++ que torna o mecanismo de herança tão poderoso: você pode criar classes derivadas e passá-las para funções existentes que conhecem apenas a classe base. Quando o código mostrado for executado, ele não vai produzir o resultado correto. Ele vai produzir a seguinte saída: 15 Essa saída indica que não apenas a totalização não funcionou, mas que os valores 100 e 200 não foram inseridos na lista quando da chamada a ManipList. Uma parte desse erro se deve a um erro no código: o parâmetro aceito por ManipList deve ser um pointer ou uma referência, do contrário nenhum valor será retornado. Modificando o protótipo de ManipList para corrigir parcialmente o problema: void ManipList(List& list) Agora teremos a seguinte saída: 15 É didático seguir a execução de ManipList passo-a-passo e observar o que acontece. Quando ocorre a chamada para a função Insert, a função invocada é List:Insert ao invés de TotalingList:Insert. Esse problema também pode ser resolvido. É possível, em C++, criar uma função com um prefixo virtual, e isso leva o C++ a sempre chamar a função na classe derivada. Ou seja, quando uma função é declarada como virtual, o compilador pode chamar versões da função que sequer existiam quando o código da função chamada foi escrito. Para verificar isso, acrescente a palavra virtual a frente das funções Insert tanto na classe List quanto na classe TotalingList, como mostrado a seguir: |
class List
{
int array[100];
int count;
public:
List(): count(0) {}
virtual void Insert(int n) { array[count++]=n; }
int Get(int i) { return array[i]; }
int Size() { return count; }
};
void ManipList(List& list)
{
// do things to the list
list.Insert(100);
list.Insert(200);
// do things to the list
}
class TotalingList: public List
{
int total;
public:
TotalingList(): List(), total(0) {}
virtual void Insert(int n)
{
total += n;
List::Insert(n);
}
int GetTotal() { return total; }
};
|
| De fato é necessário colocar a
palavra virtual apenas a frente do nome da função na classe
base, mas é um bom hábito repeti-la nas funções das classes derivadas, como uma
indicação explícita do que está ocorrendo. Agora você pode executar o programa e obter as saídas corretas: 15 O que está acontecendo? A palavra virtual a frente da função determina para o C++ que você planeja criar novas versões dessa mesma função em classes derivadas. Ou seja, virtual permite que você declare suas futuras intenções em relação à classe em definição. Quando uma função virtual é chamada, o C++ examina a classe que chamou a função, e busca a versão da função para esta classe, mesmo que a classe derivada ainda não existisse quando a chamada da função foi escrita. Isso significa que em certos casos você tem que pensar no futuro quando escreve o código. Você deve refletir eu, ou qualquer outro programador, poderia no futuro precisar modificar o comportamento dessa função? Se a resposta for sim então a função deve ser declarada como virtual. Deve-se ter alguns cuidados para que uma função virtual funcione corretamente. Por exemplo, você deve realmente prever a necessidade de se modificar o comportamento da função e lembrar-se de declará-la como virtual na classe base. Um outro ponto importante pode ser visto no exemplo acima: experimente remover o & da lista de parâmetros em ManipList e siga o código passo-a-passo. Mesmo a função Insert estando marcada como virtual na classe base, a função List::Insert é chamada, ao invés da função TotalingList::Insert. Isso acontece porque, quando o & não está presente, a lista de tipos de parâmetros em List está atuando como se fosse uma definição de conversão de tipos. Qualquer classe passada é convertida para a classe base List. Quando o & está presente na lista de parâmetros, tal conversão não acontece. Pode-se ver funções virtuais em praticamente toda hierarquia de classes em C++. Um hierarquia de classes típica espera que, no futuro, se altere o comportamento das funções para adaptar a biblioteca de classes às necessidades específicas de uma ou de outra aplicação. Funções virtuais são usadas com freqüência quando o projetista da classe não pode realmente saber o que será feito com a classe no futuro. Suponha que você está usando a interface de uma classe que implementa buttons na tela. Quando você cria uma instância do button, ele se desenha na tela e se comporta de modo padrão, ou seja, ilumina-se quando é clicado pelo usuário. Entretanto, o programador que projetou essa classe não tinha idéia do que os programadores usuários da classe poderiam querer fazer quando o button fosse clicado. Para esses casos, o projetista da classe poderia ter definido uma função virtual denominada, por exemplo, handleEvent que é chamada sempre que o button é clicado. Feito isso, você pode sobrescrever a função virtual com uma função própria que maneja a situação do modo mais adequado à sua aplicação. |
| Conclusão |
| Cobrimos uma grande quantidade de temas nessa série de
tutoriais, mas você talvez tenha a sensação de que ainda há muito o que se aprofundar
em C++. Isso é verdade em certo sentido: C++ é uma linguagem muito profunda, com algumas
sutilezas e artifícios que somente a experiência pode lhe ajudar a dominar. C também é
assim, só que em escala menor. A única maneira de se compreender completamente uma linguagem de programação é escrever, e ler, muito código. Você pode aprender muito usando, e estudando, bibliotecas criadas por outros. Todos os benefícios do C++ se tornarão mais evidentes para você na medida em que você compreenda mais e mais essa linguagem. Então... vamos à codificação! |
| © 1998 Interface Technologies, Inc by Marshall Brain Tradução de Dagoberto Haele Arnaut |
| | Home | Bookmarks | Universidades | Para Saber mais | Universidades | WEB Directory | Mapa do site | | |