Menu
CoddyTech

Mixins mediante CRTP

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

El Curiously Recurring Template Pattern (CRTP) es una técnica en la que una clase hereda de una clase base de plantilla y se pasa a sí misma como argumento de plantilla. Esto permite el polimorfismo en tiempo de compilación y permite que las clases base accedan a los miembros de las clases derivadas sin funciones virtuales.

La estructura básica de CRTP es así:

template <typename Derived>
class Base {
public:
    void interface() {
        static_cast<Derived*>(this)->implementation();
    }
};

class MyClass : public Base<MyClass> {
public:
    void implementation() {
        std::cout << "MyClass implementation\n";
    }
};

CRTP es especialmente potente para crear mixins: funcionalidad reutilizable que se puede «mezclar» en las clases. A diferencia de la herencia tradicional, los mixins añaden capacidades sin crear jerarquías profundas:

template <typename Derived>
class Printable {
public:
    void print() const {
        const Derived& self = static_cast<const Derived&>(*this);
        std::cout << self.toString() << "\n";
    }
};

class Person : public Printable<Person> {
    std::string name;
public:
    Person(const std::string& n) : name(n) {}
    std::string toString() const { return "Person: " + name; }
};

// Uso:
Person p("Alice");
p.print();  // Salida: Person: Alice

La ventaja principal es que todas las llamadas a métodos se resuelven en tiempo de compilación, eliminando la sobrecarga de las funciones virtuales. Puedes combinar varios mixins de CRTP para componer funcionalidades, lo que lo convierte en una alternativa flexible al polimorfismo en tiempo de ejecución cuando los tipos se conocen en tiempo de compilación.

challenge icon

Desafío

Fácil

Construyamos un sistema de registro que use mixins CRTP para añadir funcionalidad reutilizable a distintas clases sin la sobrecarga de las funciones virtuales. Crearás dos mixins que se pueden «mezclar» en cualquier clase: uno para contar instancias y otro para generar representaciones en forma de cadena.

Organizarás el código en tres archivos:

  • Mixins.h: Define dos plantillas de mixins CRTP que proporcionan funcionalidad reutilizable.

    Crea una plantilla de mixin Countable que haga un seguimiento de cuántas instancias de una clase derivada existen. Debe tener un contador estático que se incremente en el constructor y se reduzca en el destructor. Proporciona un método estático getCount() que devuelva el recuento actual.

    Crea una plantilla de mixin Describable que proporcione un método describe(). Este método debe usar static_cast para acceder a la clase derivada y llamar a su método getDescription(), y luego imprimir el resultado seguido de un salto de línea.

    Recuerda que los mixins CRTP usan static_cast<Derived*>(this) para acceder a los miembros de la clase derivada en tiempo de compilación.

  • Entities.h: Define dos clases de entidades que hereden de ambos mixins.

    Crea una clase Player que herede tanto de Countable<Player> como de Describable<Player>. Debe almacenar un nombre y un nivel, y proporcionar un método getDescription() que devuelva una cadena con el formato: Player: [name] (Level [level])

    Crea una clase Enemy que también herede de ambos mixins. Debe almacenar un tipo y la salud, y proporcionar un método getDescription() que devuelva: Enemy: [type] with [health] HP

    No olvides inicializar el contador estático de cada clase.

  • main.cpp: Lee cuatro entradas (cada una en una línea aparte):
    1. Nombre del jugador (cadena)
    2. Nivel del jugador (entero)
    3. Tipo de enemigo (cadena)
    4. Salud del enemigo (entero)

    Crea un Player y un Enemy con los valores dados. Después, demuestra los mixins:

    1. Imprime Player count: [count] usando el método estático getCount()
    2. Imprime Enemy count: [count]
    3. Llama a describe() para el jugador
    4. Llama a describe() para el enemigo
    5. Crea un segundo jugador con el nombre "Guest" y el nivel 1
    6. Vuelve a imprimir Player count: [count] para mostrar el recuento actualizado
    7. Llama a describe() para el segundo jugador

Por ejemplo, con las entradas Hero, 10, Dragon y 500:

Player count: 1
Enemy count: 1
Player: Hero (Level 10)
Enemy: Dragon with 500 HP
Player count: 2
Player: Guest (Level 1)

Este desafío demuestra cómo los mixins CRTP añaden funcionalidad (contar y describir) a clases no relacionadas sin usar funciones virtuales. Tanto Player como Enemy adquieren las mismas capacidades al heredar de las mismas plantillas de mixins, pero cada una mantiene su propio contador de instancias independiente porque la plantilla se instancia con tipos diferentes.

Pruébalo tú mismo

#include <iostream>
#include <string>
#include "Entities.h"

using namespace std;

int main() {
    // Lee las entradas
    string playerName;
    int playerLevel;
    string enemyType;
    int enemyHealth;
    
    cin >> playerName;
    cin >> playerLevel;
    cin >> enemyType;
    cin >> enemyHealth;
    
    // TODO: Crea un Player con el nombre y el nivel dados
    
    // TODO: Crea un Enemy con el tipo y la salud dados
    
    // TODO: Imprime "Player count: [count]" usando Player::getCount()
    
    // TODO: Imprime "Enemy count: [count]" usando Enemy::getCount()
    
    // TODO: Llama a describe() para el jugador
    
    // TODO: Llama a describe() para el enemigo
    
    // TODO: Crea un segundo jugador con el nombre "Guest" y el nivel 1
    
    // TODO: Imprime "Player count: [count]" de nuevo
    
    // TODO: Llama a describe() para el segundo jugador
    
    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