Menu
Coddy logo textTech

Funciones virtuales y VTable

Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy. Lección 52 de 104.

La palabra clave virtual resuelve el problema que vimos en la lección anterior. Cuando declaras un método como virtual, C++ determina qué versión llamar según el tipo real del objeto durante la ejecución, no según el tipo del puntero.

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();  // Salida: Woof!

¡Ahora se llama al método correcto! La palabra clave override es opcional, pero se recomienda. Le indica al compilador que pretendes sobrescribir una función virtual, detectando errores si las firmas no coinciden.

¿Cómo funciona esto? Cuando una clase tiene funciones virtuales, el compilador crea una tabla virtual (vtable). Esta es una tabla de búsqueda oculta que contiene punteros a las funciones virtuales de la clase. Cada objeto con funciones virtuales almacena un puntero oculto (vptr) a la vtable de su clase.

Cuando llamas a una función virtual a través de un puntero, el programa busca la dirección de función correcta en la vtable durante la ejecución. Esto se denomina despacho dinámico. Añade una pequeña sobrecarga en comparación con las llamadas a funciones normales, pero permite un potente comportamiento polimórfico.

Una regla importante: si una clase tiene funciones virtuales y se utilizará como clase base, su destructor también debería ser virtual. Esto garantiza una limpieza adecuada al eliminar objetos derivados mediante punteros a la clase base:

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

Desafío

Fácil

Construyamos un sistema de reproducción multimedia que demuestre el poder de las funciones virtuales y el polimorfismo en tiempo de ejecución. Crearás una jerarquía de tipos de medios en la que cada uno se reproduce de forma diferente, y verás cómo las funciones virtuales permiten el comportamiento correcto incluso cuando se accede a ellas mediante punteros a la clase base.

Organizarás tu código en tres archivos:

  • Media.h: Define una clase base Media que representa cualquier medio reproducible:
    • Un miembro protegido std::string title
    • Un constructor que recibe un título y lo almacena
    • Un método virtual play() que imprime: Playing media: <title>
    • Un destructor virtual que imprime: Media [<title>] destroyed
  • AudioTrack.h: Define una clase AudioTrack que hereda públicamente de Media:
    • Un miembro privado std::string artist
    • Un constructor que recibe un título y un artista, pasa el título a la clase base y almacena el artista
    • Sobrescribe play() usando la palabra clave override para imprimir: Playing audio: <title> by <artist>
    • Un destructor que imprime: AudioTrack [<title>] destroyed
  • main.cpp: Lee tres entradas (cada una en una línea separada):
    1. Título de la pista de audio (cadena)
    2. Nombre del artista (cadena)
    3. Título del video (cadena)

    Define una clase VideoClip directamente en main.cpp que herede públicamente de Media:

    • Un miembro privado int duration (en segundos)
    • Un constructor que reciba un título y una duración (establece la duración predeterminada en 120)
    • Sobrescribe play() para imprimir: Playing video: <title> (<duration>s)
    • Un destructor que imprime: VideoClip [<title>] destroyed

    Crea un arreglo de tres punteros Media* dentro de un ámbito de bloque. Reserva dinámicamente:

    • Un objeto base Media con el título "Generic Media"
    • Un objeto AudioTrack con el título y el artista introducidos
    • Un objeto VideoClip con el título del video introducido

    Recorre el arreglo y llama a play() en cada puntero. Después, elimina todos los objetos en orden inverso. Después del bloque, imprime: Playback complete!

Por ejemplo, con las entradas Bohemian Rhapsody, Queen y 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!

Observa cómo llamar a play() mediante punteros Media* invoca el método correcto de la clase derivada gracias a las funciones virtuales. Observa también cómo el destructor virtual garantiza una limpieza adecuada: tanto el destructor derivado como el de la clase base se ejecutan al eliminar mediante un puntero a la clase base.

Pruébalo tú mismo

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

using namespace std;

// TODO: Define la clase VideoClip aquí que hereda públicamente de Media
// - Miembro privado int duration (en segundos)
// - El constructor toma title y duration (duration por defecto a 120)
// - Override play() to print: Playing video: <title> (<duration>s)
// - Destructor prints: VideoClip [<title>] destroyed

class VideoClip : public Media {
private:
    int duration;

public:
    // TODO: Implementa el constructor
    VideoClip(const std::string& t, int d = 120) : Media(t) {
        // TODO: Almacena la duration
    }

    // TODO: Sobrescribe el método play()
    void play() override {
        // TODO: Implementa este método
    }

    // TODO: Implementa el destructor
    ~VideoClip() {
        // TODO: Implementa este destructor
    }
};

int main() {
    // Lee las entradas
    string audioTitle;
    string artist;
    string videoTitle;
    
    getline(cin, audioTitle);
    getline(cin, artist);
    getline(cin, videoTitle);

    // TODO: Crea un ámbito de bloque con llaves
    // Dentro del bloque:
    // 1. Crea un array de tres punteros Media*
    // 2. Asigna dinámicamente:
    //    - A base Media object with title "Generic Media"
    //    - Un objeto AudioTrack con el título y el artista de entrada
    //    - Un objeto VideoClip con el título de video de entrada
    // 3. Recorre el array y llama a play() en cada puntero
    // 4. Elimina todos los objetos en orden inverso

    // TODO: After the block, print: Playback complete!
    
    return 0;
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación Orientada a Objetos

Practica por tu cuenta: Compilador de C++ online