Menu
Coddy logo textTech

Mixin tramite CRTP

Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 77 di 104.

Il Curiously Recurring Template Pattern (CRTP) è una tecnica in cui una classe eredita da una classe base modello, passando se stessa come argomento del modello. Questo consente il polimorfismo a tempo di compilazione e permette alle classi base di accedere ai membri delle classi derivate senza funzioni virtuali.

La struttura di base di CRTP è questa:

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";
    }
};

Il CRTP è particolarmente potente per creare mixins - funzionalità riutilizzabili che possono essere «inserite» nelle classi. A differenza dell’ereditarietà tradizionale, i mixin aggiungono funzionalità senza creare gerarchie profonde:

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; }
};

// Utilizzo:
Person p("Alice");
p.print();  // Output: Person: Alice

Il vantaggio principale è che tutte le chiamate ai metodi vengono risolte in fase di compilazione, eliminando l'overhead delle funzioni virtuali. Puoi combinare più mixin CRTP per comporre le funzionalità, rendendolo un'alternativa flessibile al polimorfismo a runtime quando i tipi sono noti in fase di compilazione.

challenge icon

Sfida

Facile

Creiamo un sistema di logging che usa i mixin CRTP per aggiungere funzionalità riutilizzabili a classi diverse senza l'overhead delle funzioni virtuali. Creerai due mixin che possono essere «mescolati» in qualsiasi classe: uno per contare le istanze e uno per generare rappresentazioni sotto forma di stringa.

Organizzerai il codice in tre file:

  • Mixins.h: definisci due template di mixin CRTP che forniscono funzionalità riutilizzabili.

    Crea un template di mixin Countable che tiene traccia del numero di istanze di una classe derivata. Dovrebbe avere un contatore statico che viene incrementato nel costruttore e decrementato nel distruttore. Fornisci un metodo statico getCount() che restituisce il conteggio attuale.

    Crea un template di mixin Describable che fornisce un metodo describe(). Questo metodo dovrebbe usare static_cast per accedere alla classe derivata e chiamarne il metodo getDescription(), quindi stampare il risultato seguito da un carattere di nuova riga.

    Ricorda che i mixin CRTP usano static_cast<Derived*>(this) per accedere ai membri della classe derivata in fase di compilazione.

  • Entities.h: definisci due classi entità che ereditano da entrambi i mixin.

    Crea una classe Player che eredita sia da Countable<Player> sia da Describable<Player>. Dovrebbe memorizzare un nome e un livello, e fornire un metodo getDescription() che restituisce una stringa nel formato: Player: [name] (Level [level])

    Crea una classe Enemy che eredita anch'essa da entrambi i mixin. Dovrebbe memorizzare un tipo e la salute, e fornire un metodo getDescription() che restituisce: Enemy: [type] with [health] HP

    Non dimenticare di inizializzare il contatore statico per ciascuna classe.

  • main.cpp: leggi quattro input (ciascuno su una riga separata):
    1. Nome del giocatore (stringa)
    2. Livello del giocatore (intero)
    3. Tipo di nemico (stringa)
    4. Salute del nemico (intero)

    Crea un Player e un Enemy con i valori forniti. Poi mostra i mixin in azione:

    1. Stampa Player count: [count] usando il metodo statico getCount()
    2. Stampa Enemy count: [count]
    3. Chiama describe() sul giocatore
    4. Chiama describe() sul nemico
    5. Crea un secondo giocatore con nome "Guest" e livello 1
    6. Stampa di nuovo Player count: [count] per mostrare il conteggio aggiornato
    7. Chiama describe() sul secondo giocatore

Per esempio, con gli input Hero, 10, Dragon e 500:

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

Questa sfida mostra come i mixin CRTP aggiungano funzionalità (conteggio e descrizione) a classi non correlate senza usare funzioni virtuali. Sia Player sia Enemy acquisiscono le stesse capacità ereditando dagli stessi template di mixin, ma ciascuno mantiene un proprio contatore delle istanze, perché il template viene istanziato con tipi diversi.

Provalo tu

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

using namespace std;

int main() {
    // Leggi i dati in ingresso
    string playerName;
    int playerLevel;
    string enemyType;
    int enemyHealth;
    
    cin >> playerName;
    cin >> playerLevel;
    cin >> enemyType;
    cin >> enemyHealth;
    
    // TODO: Crea un Player con il nome e il livello indicati
    
    // TODO: Crea un Enemy con il tipo e la salute indicati
    
    // TODO: Stampa "Player count: [count]" usando Player::getCount()
    
    // TODO: Stampa "Enemy count: [count]" usando Enemy::getCount()
    
    // TODO: Chiama describe() sul giocatore
    
    // TODO: Chiama describe() sul nemico
    
    // TODO: Crea un secondo giocatore con nome "Guest" e livello 1
    
    // TODO: Stampa di nuovo "Player count: [count]"
    
    // TODO: Chiama describe() sul secondo giocatore
    
    return 0;
}
quiz iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Compilatore C++ online