0Pricing
C++ Academy · Leçon

L’idiome CRTP

Comprendre le motif de modèle à récurrence curieuse

L’idiome CRTP est une leçon C++ Academy gratuite sur CoddyKit. Ceci est la leçon 1 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage C++ Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours C++ Academy comprend 4 leçons au total.

Qu’est-ce que CRTP ?

Le modèle de programmation à récurrence curieuse (CRTP) est une idiome C++ dans lequel une classe Derived hérite d’une classe de base de modèle instanciée avec Derived elle-même.

  • Forme : class Derived : public Base<Derived>
  • La classe de base connaît son type dérivé à la compilation
template <typename T>
class Base {};

class Derived : public Base<Derived> {};

Le mécanisme central

Dans la classe de base, vous pouvez appliquer static_cast au pointeur this pour obtenir le type dérivé. Comme le type dérivé est un paramètre de modèle, il s’agit d’une conversion entièrement effectuée à la compilation, sans coût à l’exécution.

#include <iostream>

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

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

int main() {
    Derived d;
    d.interface();
    return 0;
}

Pourquoi « récurrence curieuse » ?

Le nom vient d’un paradoxe apparent : la classe de base fait référence à la classe dérivée, qui n’est pas encore entièrement définie lorsque le modèle de base est écrit.

  • Cela fonctionne parce que les modèles ne sont instanciés que lorsqu’ils sont utilisés.
  • Au moment de l’instanciation, Derived est un type complet.

Une première utilisation concrète

CRTP permet à une classe de base de fournir des fonctionnalités réutilisables qui rappellent la classe dérivée. Ici, la classe de base fournit une fonction print() qui délègue l’appel à name() dans la classe dérivée.

#include <iostream>
#include <string>

template <typename T>
struct Named {
    void print() {
        std::cout << static_cast<T*>(this)->name() << "\n";
    }
};

struct Cat : Named<Cat> {
    std::string name() { return "Cat"; }
};

int main() {
    Cat c;
    c.print();
    return 0;
}

Aucune table virtuelle

Contrairement au polymorphisme à l’exécution, CRTP n’a besoin ni du mot-clé virtual ni d’un pointeur vers une table virtuelle dans chaque objet. La résolution de l’appel s’effectue entièrement à la compilation.

  • Des objets plus petits, sans pointeur caché vers une table virtuelle
  • Les appels peuvent être inline

Compter les instances

Une application classique de CRTP est le compteur d’objets. Chaque classe dérivée reçoit automatiquement son propre compteur statique.

#include <iostream>

template <typename T>
struct Counter {
    static inline int count = 0;
    Counter() { ++count; }
    ~Counter() { --count; }
};

struct Widget : Counter<Widget> {};

int main() {
    Widget a, b;
    std::cout << Widget::count << "\n";
    return 0;
}

Chaque Derived possède son propre état

Comme Counter<Widget> et Counter<Gadget> sont des instanciations distinctes du modèle, chacune possède un count statique indépendant. Cette séparation est impossible avec une seule classe de base qui ne serait pas un modèle.

#include <iostream>

template <typename T>
struct Counter {
    static inline int count = 0;
    Counter() { ++count; }
};

struct A : Counter<A> {};
struct B : Counter<B> {};

int main() {
    A a1, a2;
    B b1;
    std::cout << A::count << " " << B::count << "\n";
    return 0;
}

static_cast est sûr ici

La conversion vers le type dérivé effectuée dans la classe de base est sûre uniquement si chaque objet de type Base<T> est réellement un T. CRTP le garantit par construction, puisque T est la classe qui hérite.

  • N’instanciez jamais Base<Wrong> pour une classe sans rapport.

Opérateurs de comparaison avec CRTP

Vous pouvez générer un ensemble complet d’opérateurs de comparaison à partir d’un seul operator< en héritant d’un utilitaire de comparaison.

#include <iostream>

template <typename T>
struct Comparable {
    friend bool operator>(const T& a, const T& b) { return b < a; }
    friend bool operator==(const T& a, const T& b) { return !(a < b) && !(b < a); }
};

struct Num : Comparable<Num> {
    int v;
    Num(int x) : v(x) {}
    friend bool operator<(const Num& a, const Num& b) { return a.v < b.v; }
};

int main() {
    std::cout << (Num(3) > Num(2)) << "\n";
    return 0;
}

Protéger le constructeur

Pour empêcher toute mauvaise utilisation accidentelle de la classe de base, donnez-lui un constructeur protected, afin que seules les classes dérivées puissent l’instancier.

#include <iostream>

template <typename T>
class Base {
protected:
    Base() = default;
public:
    void run() { static_cast<T*>(this)->step(); }
};

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

int main() {
    Job j;
    j.run();
    return 0;
}

CRTP dans les bibliothèques standard

CRTP apparaît dans de nombreuses bibliothèques C++ réelles.

  • std::enable_shared_from_this<T> l’utilise
  • de nombreuses bibliothèques mathématiques fondées sur des modèles d’expressions, comme Eigen, s’appuient dessus
  • les plages et les utilitaires pour itérateurs utilisent des motifs similaires

Reconnaître la forme X : Base<X> vous aide à lire du code avancé.

Vérification rapide

Vérifiez votre compréhension de la forme CRTP.

Récapitulatif

Vous avez découvert l’idiome CRTP.

  • Derived : Base<Derived> fournit à la classe de base son type concret
  • Un static_cast<T*>(this) permet la sélection à la compilation
  • Pas de table virtuelle, des objets plus petits et des appels pouvant être intégrés
  • Utilisé pour les compteurs, les classes d’ajout de comparaison et les utilitaires de bibliothèques

Questions Fréquemment Posées

La leçon « L’idiome CRTP » est-elle gratuite ?

Oui — le texte complet de « L’idiome CRTP » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours C++ Academy, passe à CoddyKit PRO. Le cours C++ Academy comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « L’idiome CRTP » ?

Comprendre le motif de modèle à récurrence curieuse Tu pratiques C++ Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer C++ Academy ?

Aucune expérience préalable n'est requise. C++ Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 1 sur 4.

Combien de temps prend la leçon « L’idiome CRTP » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon C++ Academy ?

Oui. Chaque leçon C++ Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. L’idiome CRTP
  2. Polymorphisme statique
  3. Mixins avec CRTP
  4. Quand utiliser CRTP
← Retour à C++ Academy