Menu
CoddyTech

Mixins via CRTP

Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy. Leçon 77 sur 104.

Le Curiously Recurring Template Pattern (CRTP) est une technique où une classe hérite d’une classe de base modèle en se passant elle-même comme argument de modèle. Cela permet le polymorphisme à la compilation et autorise les classes de base à accéder aux membres des classes dérivées sans fonctions virtuelles.

La structure de base du CRTP ressemble à ceci :

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 est particulièrement puissant pour créer des mixins — des fonctionnalités réutilisables qui peuvent être « intégrées » aux classes. Contrairement à l’héritage traditionnel, les mixins ajoutent des capacités sans créer de hiérarchies profondes :

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

// Utilisation :
Person p("Alice");
p.print();  // Sortie : Person: Alice

L’avantage principal est que tous les appels de méthode sont résolus à la compilation, ce qui élimine le surcoût des fonctions virtuelles. Tu peux combiner plusieurs mixins CRTP pour composer des fonctionnalités, ce qui en fait une solution flexible de remplacement du polymorphisme à l’exécution lorsque les types sont connus à la compilation.

challenge icon

Défi

Facile

Construisons un système de journalisation qui utilise des mixins CRTP pour ajouter des fonctionnalités réutilisables à différentes classes sans le surcoût des fonctions virtuelles. Tu vas créer deux mixins qui peuvent être « intégrés » à n’importe quelle classe : l’un pour compter les instances et l’autre pour générer des représentations sous forme de chaînes.

Tu vas répartir ton code sur trois fichiers :

  • Mixins.h : définis deux modèles de mixins CRTP qui fournissent des fonctionnalités réutilisables.

    Crée un modèle de mixin Countable qui suit le nombre d’instances d’une classe dérivée. Il doit avoir un compteur statique qui s’incrémente dans le constructeur et se décrémente dans le destructeur. Fournis une méthode statique getCount() qui renvoie le nombre actuel.

    Crée un modèle de mixin Describable qui fournit une méthode describe(). Cette méthode doit utiliser static_cast pour accéder à la classe dérivée et appeler sa méthode getDescription(), puis afficher le résultat suivi d’un saut de ligne.

    N’oublie pas que les mixins CRTP utilisent static_cast<Derived*>(this) pour accéder aux membres de la classe dérivée à la compilation.

  • Entities.h : définis deux classes d’entités qui héritent des deux mixins.

    Crée une classe Player qui hérite à la fois de Countable<Player> et de Describable<Player>. Elle doit stocker un nom et un niveau, et fournir une méthode getDescription() qui renvoie une chaîne au format : Player: [name] (Level [level])

    Crée une classe Enemy qui hérite également des deux mixins. Elle doit stocker un type et des points de vie, et fournir une méthode getDescription() qui renvoie : Enemy: [type] with [health] HP

    N’oublie pas d’initialiser le compteur statique pour chaque classe.

  • main.cpp : lis quatre valeurs (chacune sur une ligne distincte) :
    1. Nom du joueur (chaîne)
    2. Niveau du joueur (entier)
    3. Type d’ennemi (chaîne)
    4. Points de vie de l’ennemi (entier)

    Crée un joueur et un ennemi avec les valeurs données. Puis montre les mixins en action :

    1. Affiche Player count: [count] à l’aide de la méthode statique getCount()
    2. Affiche Enemy count: [count]
    3. Appelle describe() sur le joueur
    4. Appelle describe() sur l’ennemi
    5. Crée un deuxième joueur nommé "Guest" de niveau 1
    6. Affiche à nouveau Player count: [count] pour montrer le nombre mis à jour
    7. Appelle describe() sur le deuxième joueur

Par exemple, avec les entrées Hero, 10, Dragon et 500 :

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

Ce défi montre comment les mixins CRTP ajoutent des fonctionnalités (comptage et description) à des classes sans lien entre elles, sans utiliser de fonctions virtuelles. Player et Enemy acquièrent les mêmes capacités en héritant des mêmes modèles de mixins, mais chacune conserve son propre compteur d’instances, car le modèle est instancié avec des types différents.

Essayez vous-même

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

using namespace std;

int main() {
    // Lire les entrées
    string playerName;
    int playerLevel;
    string enemyType;
    int enemyHealth;
    
    cin >> playerName;
    cin >> playerLevel;
    cin >> enemyType;
    cin >> enemyHealth;
    
    // TODO: Créer un Player avec le nom et le niveau donnés
    
    // TODO: Créer un Enemy avec le type et les points de vie donnés
    
    // TODO: Afficher "Player count: [count]" en utilisant Player::getCount()
    
    // TODO: Afficher "Enemy count: [count]" en utilisant Enemy::getCount()
    
    // TODO: Appeler describe() sur le joueur
    
    // TODO: Appeler describe() sur l’ennemi
    
    // TODO: Créer un deuxième joueur avec le nom "Guest" et le niveau 1
    
    // TODO: Afficher à nouveau "Player count: [count]"
    
    // TODO: Appeler describe() sur le deuxième joueur
    
    return 0;
}
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation Orientée Objet

Entraînez-vous par vous-même : Compilateur C++ en ligne