Menu
Coddy logo textTech

Mixins mittels CRTP

Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 77 von 104.

Das Curiously Recurring Template Pattern (CRTP) ist eine Technik, bei der eine Klasse von einer Template-Basisklasse erbt und sich selbst als Template-Argument übergibt. Dies ermöglicht Polymorphismus zur Compile-Zeit und erlaubt es Basisklassen, ohne virtuelle Funktionen auf Member der abgeleiteten Klasse zuzugreifen.

Die grundlegende CRTP-Struktur sieht so aus:

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 ist besonders leistungsfähig zum Erstellen von Mixins – wiederverwendbare Funktionalität, die in Klassen „eingemischt“ werden kann. Im Gegensatz zur herkömmlichen Vererbung fügen Mixins Fähigkeiten hinzu, ohne tiefe Hierarchien zu erzeugen:

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

// Verwendung:
Person p("Alice");
p.print();  // Ausgabe: Person: Alice

Der entscheidende Vorteil besteht darin, dass alle Methodenaufrufe zur Kompilierzeit aufgelöst werden, wodurch der Overhead virtueller Funktionen entfällt. Du kannst mehrere CRTP-Mixins kombinieren, um Funktionalität zusammenzusetzen, was eine flexible Alternative zum Laufzeitpolymorphismus darstellt, wenn die Typen zur Kompilierzeit bekannt sind.

challenge icon

Aufgabe

Einfach

Erstellen wir ein Protokollierungssystem, das CRTP-Mixins verwendet, um verschiedenen Klassen wiederverwendbare Funktionalität hinzuzufügen, ohne den Overhead virtueller Funktionen. Du erstellst zwei Mixins, die in jede Klasse „eingemischt“ werden können: eines zum Zählen von Instanzen und eines zum Erzeugen von Zeichenfolgendarstellungen.

Du organisierst deinen Code über drei Dateien:

  • Mixins.h: Definiere zwei CRTP-Mixin-Templates, die wiederverwendbare Funktionalität bereitstellen.

    Erstelle ein Countable-Mixin-Template, das verfolgt, wie viele Instanzen einer abgeleiteten Klasse existieren. Es sollte einen statischen Zähler besitzen, der im Konstruktor erhöht und im Destruktor verringert wird. Stelle eine statische Methode getCount() bereit, die den aktuellen Zählerstand zurückgibt.

    Erstelle ein Describable-Mixin-Template, das eine describe()-Methode bereitstellt. Diese Methode sollte static_cast verwenden, um auf die abgeleitete Klasse zuzugreifen und deren Methode getDescription() aufzurufen, und anschließend das Ergebnis gefolgt von einem Zeilenumbruch ausgeben.

    Denke daran, dass CRTP-Mixins static_cast<Derived*>(this) verwenden, um zur Compile-Zeit auf die Member der abgeleiteten Klasse zuzugreifen.

  • Entities.h: Definiere zwei Entitätsklassen, die von beiden Mixins erben.

    Erstelle eine Player-Klasse, die sowohl von Countable<Player> als auch von Describable<Player> erbt. Sie sollte einen Namen und ein Level speichern und eine getDescription()-Methode bereitstellen, die eine Zeichenfolge im Format Player: [name] (Level [level]) zurückgibt.

    Erstelle eine Enemy-Klasse, die ebenfalls von beiden Mixins erbt. Sie sollte einen Typ und Lebenspunkte speichern und eine getDescription()-Methode bereitstellen, die Folgendes zurückgibt: Enemy: [type] with [health] HP

    Vergiss nicht, den statischen Zähler für jede Klasse zu initialisieren.

  • main.cpp: Lies vier Eingaben ein (jeweils in einer eigenen Zeile):
    1. Player-Name (Zeichenfolge)
    2. Player-Level (Ganzzahl)
    3. Enemy-Typ (Zeichenfolge)
    4. Enemy-Lebenspunkte (Ganzzahl)

    Erstelle mit den angegebenen Werten einen Player und einen Enemy. Demonstriere anschließend die Mixins:

    1. Gib Player count: [count] mithilfe der statischen Methode getCount() aus
    2. Gib Enemy count: [count] aus
    3. Rufe describe() für den Player auf
    4. Rufe describe() für den Enemy auf
    5. Erstelle einen zweiten Player mit dem Namen "Guest" und dem Level 1
    6. Gib erneut Player count: [count] aus, um den aktualisierten Zählerstand zu zeigen
    7. Rufe describe() für den zweiten Player auf

Beispielsweise bei den Eingaben Hero, 10, Dragon und 500:

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

Diese Aufgabe zeigt, wie CRTP-Mixins nicht verwandten Klassen Funktionalität (Zählen und Beschreiben) hinzufügen, ohne virtuelle Funktionen zu verwenden. Sowohl Player als auch Enemy erhalten durch die Vererbung von denselben Mixin-Templates dieselben Fähigkeiten, aber jede Klasse verwaltet ihren eigenen separaten Instanzzähler, da das Template mit unterschiedlichen Typen instanziiert wird.

Probier es selbst

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

using namespace std;

int main() {
    // Eingaben lesen
    string playerName;
    int playerLevel;
    string enemyType;
    int enemyHealth;
    
    cin >> playerName;
    cin >> playerLevel;
    cin >> enemyType;
    cin >> enemyHealth;
    
    // TODO: Erstelle einen Player mit dem angegebenen Namen und Level
    
    // TODO: Erstelle einen Enemy mit dem angegebenen Typ und Gesundheit
    
    // TODO: Gib "Player count: [count]" unter Verwendung von Player::getCount() aus
    
    // TODO: Gib "Enemy count: [count]" unter Verwendung von Enemy::getCount() aus
    
    // TODO: Rufe describe() für den Player auf
    
    // TODO: Rufe describe() für den Enemy auf
    
    // TODO: Erstelle einen zweiten Player mit dem Namen "Guest" und Level 1
    
    // TODO: Gib "Player count: [count]" erneut aus
    
    // TODO: Rufe describe() für den zweiten Player auf
    
    return 0;
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Online-C++-Compiler