Menu
Coddy logo textTech

Funções virtuais e VTable

Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy. Lição 52 de 104.

A palavra-chave virtual resolve o problema que vimos na lição anterior. Quando você declara um método como virtual, o C++ determina qual versão chamar com base no tipo real do objeto em tempo de execução, não no tipo do ponteiro.

class Animal {
public:
    virtual void speak() {
        std::cout << "Some sound" << std::endl;
    }
};

class Dog : public Animal {
public:
    void speak() override {
        std::cout << "Woof!" << std::endl;
    }
};

Dog d;
Animal* ptr = &d;
ptr->speak();  // Saída: Woof!

Agora o método correto é chamado! A palavra-chave override é opcional, mas recomendada. Ela informa ao compilador que você pretende substituir uma função virtual, identificando erros caso as assinaturas não correspondam.

Como isso funciona? Quando uma classe tem funções virtuais, o compilador cria uma tabela virtual (vtable). Essa é uma tabela de consulta oculta que contém ponteiros para as funções virtuais da classe. Cada objeto com funções virtuais armazena um ponteiro oculto (vptr) para a vtable de sua classe.

Quando você chama uma função virtual por meio de um ponteiro, o programa procura o endereço correto da função na vtable em tempo de execução. Isso é chamado de despacho dinâmico. Ele adiciona uma pequena sobrecarga em comparação com chamadas de função comuns, mas possibilita um poderoso comportamento polimórfico.

Uma regra importante: se uma classe tiver funções virtuais e for usada como classe base, seu destrutor também deverá ser virtual. Isso garante a limpeza adequada ao excluir objetos derivados por meio de ponteiros para a classe base:

class Animal {
public:
    virtual ~Animal() {}  // Destrutor virtual
    virtual void speak() {}
};
challenge icon

Desafio

Fácil

Vamos criar um sistema de reprodução de mídia que demonstra o poder das funções virtuais e do polimorfismo em tempo de execução. Você criará uma hierarquia de tipos de mídia em que cada um é reproduzido de maneira diferente e verá como as funções virtuais habilitam o comportamento correto mesmo quando acessadas por meio de ponteiros para a classe base.

Você organizará seu código em três arquivos:

  • Media.h: Defina uma classe base Media que represente qualquer mídia reproduzível:
    • Um membro std::string title protegido
    • Um construtor que receba um título e o armazene
    • Um método virtual play() que imprima: Playing media: <title>
    • Um destrutor virtual que imprima: Media [<title>] destroyed
  • AudioTrack.h: Defina uma classe AudioTrack que herde publicamente de Media:
    • Um membro privado std::string artist
    • Um construtor que receba um título e um artista, passe o título para a classe base e armazene o artista
    • Substitua play() usando a palavra-chave override para imprimir: Playing audio: <title> by <artist>
    • Um destrutor que imprima: AudioTrack [<title>] destroyed
  • main.cpp: Leia três entradas (cada uma em uma linha separada):
    1. Título da faixa de áudio (string)
    2. Nome do artista (string)
    3. Título do vídeo (string)

    Defina uma classe VideoClip diretamente em main.cpp que herde publicamente de Media:

    • Um membro privado int duration (em segundos)
    • Um construtor que receba um título e uma duração (com duração padrão de 120)
    • Substitua play() para imprimir: Playing video: <title> (<duration>s)
    • Um destrutor que imprima: VideoClip [<title>] destroyed

    Crie um array de três ponteiros Media* dentro de um escopo de bloco. Aloque dinamicamente:

    • Um objeto base Media com o título "Generic Media"
    • Um objeto AudioTrack com o título e o artista informados
    • Um objeto VideoClip com o título do vídeo informado

    Percorra o array e chame play() em cada ponteiro. Em seguida, exclua todos os objetos na ordem inversa. Após o bloco, imprima: Playback complete!

Por exemplo, com as entradas Bohemian Rhapsody, Queen e Nature Documentary:

Playing media: Generic Media
Playing audio: Bohemian Rhapsody by Queen
Playing video: Nature Documentary (120s)
VideoClip [Nature Documentary] destroyed
Media [Nature Documentary] destroyed
AudioTrack [Bohemian Rhapsody] destroyed
Media [Bohemian Rhapsody] destroyed
Media [Generic Media] destroyed
Playback complete!

Observe como chamar play() por meio de ponteiros Media* invoca o método correto da classe derivada graças às funções virtuais. Observe também como o destrutor virtual garante a limpeza adequada: tanto os destrutores da classe derivada quanto os da classe base são executados ao excluir por meio de um ponteiro para a classe base.

Experimente você mesmo

#include <iostream>
#include <string>
#include "Media.h"
#include "AudioTrack.h"

using namespace std;

// TODO: Defina a classe VideoClip aqui que herda publicamente de Media
// - Membro privado int duration (em segundos)
// - Construtor recebe title e duration (duration padrão para 120)
// - Override play() to print: Playing video: <title> (<duration>s)
// - Destructor prints: VideoClip [<title>] destroyed

class VideoClip : public Media {
private:
    int duration;

public:
    // TODO: Implemente o construtor
    VideoClip(const std::string& t, int d = 120) : Media(t) {
        // TODO: Armazene o duration
    }

    // TODO: Sobrescreva o método play()
    void play() override {
        // TODO: Implemente este método
    }

    // TODO: Implemente o destrutor
    ~VideoClip() {
        // TODO: Implemente este destrutor
    }
};

int main() {
    // Leia as entradas
    string audioTitle;
    string artist;
    string videoTitle;
    
    getline(cin, audioTitle);
    getline(cin, artist);
    getline(cin, videoTitle);

    // TODO: Crie um escopo de bloco com chaves
    // Dentro do bloco:
    // 1. Crie um array de três ponteiros Media*
    // 2. Aloque dinamicamente:
    //    - A base Media object with title "Generic Media"
    //    - Um objeto AudioTrack com o título e o artista de entrada
    //    - Um objeto VideoClip com o título do vídeo de entrada
    // 3. Percorra o array e chame play() em cada ponteiro
    // 4. Delete todos os objetos em ordem inversa

    // TODO: After the block, print: Playback complete!
    
    return 0;
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos

Pratique por conta própria: Compilador de C++ online