FAQ Lite
Herança - Funções Virtuais

[ 20.1 ] O que é uma função membro virtual?
[ 20.2 ] Como o C++ consegue ligação dinâmica (dynamic binding) simultaneamente à verificação estática (static typing)?
[ 20.3 ] Qual é a diferença entre os modos como são chamadas as funções membro não-virtuais e funções membro virtuais?
[ 20.4 ] Quando meu destrutor deve ser virtual?
[ 20.5 ] O que é um construtor virtual?

[ 20.1 ] O que é uma função membro virtual?

Do ponto de vista de orientação a objeto, é o recurso mais importante do C++.
Vide [ 6.8 ] e [ 6.9 ].

O mecanismo de função virtual permite que classes derivadas substituam a implementação realizada na classe base. O compilador certifica-se que a substituição seja sempre invocada quando o objeto em questão é realmente da classe derivada, seja esse objeto acessado por um pointer para classe base, ao invés de por um pointer para a classe derivada.

Isso permite que algoritmos na classe base sejam substituídos por outros nas classes derivadas, mesmo que as classes clientes não conheçam as classes derivadas que contem as novas versões do algoritmo.

A classe derivada pode tanto substituir totalmente (sobrescrever) a função membro da classe base, quanto substituí-la parcialmente (ampliar). A substituição parcial é realizada quando a função membro na classe derivada invoca a função membro na classe base.

Topo
[ 20.2 ] Como o C++ consegue ligação dinâmica (dynamic binding) simultaneamente à verificação estática (static typing)?

Quando você tem um pointer para um objeto, esse objeto pode realmente ser de uma classe derivada da classe original do pointer. Por exemplo, um Vehicle* que na realidade aponta para um objeto Car. Essa situação tem dois tipos: um tipo estático de pointer (Vehicle, no caso), e um tipo dinâmico de objeto para o qual aponta (Car, no caso).

Verificação estática significa que a validade da invocação a uma função membro é verificada o mais cedo possível pelo compilador, em tempo de compilação. O compilador usa a característica estática do pointer para determinar se a invocação à função membro é legal. Se o pointer pode acessar a função membro, certamente que o objeto apontado também pode acessá-la. Por exemplo, se Vehicle tem uma determinada função membro, é certo que Car tem acesso a essa mesma função membro, já que Car é um tipo de Vehicle.

Ligação dinâmica significa que o endereço do código, em uma invocação a uma função membro, é determinado o mais tarde possível, com base na dinâmica dos tipos de objeto em tempo de execução. Isso é ligação dinâmica porque a ligação com o código que é realmente chamado é estabelecida dinamicamente, em tempo de execução. Ligação dinâmica é um resultado de funções virtuais.

Topo
[ 20.3 ] Qual é a diferença entre os modos como são chamadas as funções membro não-virtuais e funções membro virtuais?

Funções membro não-virtuais são resolvidas estaticamente. Ou seja, a função membro é selecionada estaticamente, em tempo de compilação, com base no tipo de pointer (ou referência) para o objeto.

Funções membro virtuais, ao contrário, são resolvidas dinamicamente, em tempo de execução. Ou seja, a função membro é selecionada dinamicamente, em tempo de execução, com base no tipo de objeto, e não com base no pointer ou referência para o objeto. Isso é denominado ligação dinâmica.

A maioria dos compiladores usam alguma variante da seguinte técnica: se o objeto tem uma ou mais funções virtuais, o compilador coloca dentro do objeto um pointer oculto, denominado virtual-pointer ou apenas v-pointer. Esse v-pointer aponta para uma tabela global denominada virtual-table ou v-table. O compilador cria uma v-table para cada classe que possua pelo menos uma função virtual. Por exemplo, se a classe Circle tem as funções virtuais draw() e move() e resize(), haverá uma, e uma única, v-table associada a classe Circle, independente da quantidade de objetos Circle existentes, e o v-pointer de cada um dos objetos Circle apontam para a v-table da classe. A v-table contem pointers para cada uma das funções virtuais da classe. Em nosso exemplo, a v-table tem três pointers: um pointer para Circle::draw(), um pointer para Circle::move() e um pointer para Circle::resize().

Durante a ativação de uma função virtual, o sistema de execução segue o v-pointer do objeto para a v-table da classe, e então seleciona o elemento da v-table correspondente à função em questão.

O consumo de espaço adicional para implementação dessa técnica é facilmente determinável: um pointer extra para cada objeto (mas apenas para objetos que podem precisar de ligação dinâmica), mais um pointer extra por função (mas apenas para funções virtuais).

Topo
[ 20.4 ] Quando meu destrutor deve ser virtual?

Quando você pode excluir um objeto derivado via um pointer da classe base.

Funções virtuais ligam-se ao código associado com a classe do objeto, ao invés de com a classe do pointer ou referência. Quando você diz delete basePtr, e a classe base tem um destrutor virtual, o destrutor que é de fato invocado é o associado com o tipo de objeto *basePtr, ao invés do associado com o tipo de pointer.

Em termos estritamente técnicos, você precisa de um destrutor virtual para a classe base se, e somente se, pretende permitir que um usuário da classe invoque o destrutor do objeto via um pointer da classe base (isso é normalmente realizado implicitamente via delete) e o objeto a ser destruído é de uma classe derivada que possui um destrutor não-usual. Uma classe tem um destrutor não-usual quando tem um destrutor explícito, ou quando tem um objeto membro ou uma classe base que tem um destrutor não-usual (perceba que essa é uma definição recursiva, ou seja, uma classe tem um destrutor não-usual quando tem um objeto membro (o qual contém uma classe base (a qual contem um objeto membro .....

Se a regra anterior lhe pareceu muito complicada, considere as coisas simplificadamente da seguinte maneira: uma classe deve ter um destrutor virtual, exceto quando não tem funções virtuais.

Racionalmente: se você tem uma função virtual qualquer na classe, você está liberando sua classe base para modificações que serão feitas nas classes derivadas, e uma dessas modificações pode ser invocar um destrutor, normalmente feito implicitamente via delete. Mais ainda, uma vez que você incluiu uma função virtual em sua classe, você já incorreu no custo adicional de memória para criação de virtual-pointer e virtual-table, portanto definir um destrutor virtual não vai implicar em nenhum consumo extra de memória que seja significativo.

Topo
[ 20.5 ] O que é um construtor virtual?
Um artifício de linguagem que permite que você faça algumas coisas que o C++ não suporta diretamente.

Você pode obter o efeito de um construtor virtual implementando um função membro
virtual clone()
- para e construção de cópia - é uma função membro
virtual create()
- para o construtor default.

    class Shape {
    public:
      virtual ~Shape() { }                 // A virtual destructor
      virtual void draw() = 0;             // A pure virtual function
      virtual void move() = 0;
      // ...
      virtual Shape* clone()  const = 0;   // Uses the copy constructor
      virtual Shape* create() const = 0;   // Uses the default constructor
    };
    
    class Circle : public Shape {
    public:
      Circle* clone()  const { return new Circle(*this); }
      Circle* create() const { return new Circle();      }
      // ...
    };     
Na função membro clone(), o código new Circle(*this) chama o construtor de cópia para copiar o estado atual de this para o recém criado objeto Circle.

Na função membro create(), o código new Circle() chama o construtor default de Circle.

Os usuários da classe podem usar essas funções membro como se fossem construtores virtuais.

    void userCode(Shape& s)
    {
      Shape* s2 = s.clone();
      Shape* s3 = s.create();
      // ...
      delete s2;    // You probably need a virtual destructor here
      delete s3;
    } 
Essa função vai operar corretamente indenpendemente de Shape ser um Circle, Square ou qualquer outro tipo de Shape.
Topo Anterior Próximo Índice
C++ FAQ Lite
Copyright © 1991-98 by Marshall Cline Ph.D., cline@parashift.com
Tradução: Dagoberto Haele Arnaut

| Home | Bookmarks | Universidades | Para Saber mais | Universidades | WEB Directory | Mapa do site |