Menu
Coddy logo textTech

Mixins via CRTP

Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy. Lição 77 de 104.

O Curiously Recurring Template Pattern (CRTP) é uma técnica na qual uma classe herda de uma classe base de template, passando a si mesma como argumento do template. Isso possibilita o polimorfismo em tempo de compilação e permite que as classes base acessem membros da classe derivada sem funções virtuais.

A estrutura básica do CRTP é assim:

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 é particularmente poderoso para criar mixins — funcionalidades reutilizáveis que podem ser “incorporadas” às classes. Ao contrário da herança tradicional, os mixins adicionam recursos sem criar hierarquias 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();  // Saída: Person: Alice

A principal vantagem é que todas as chamadas de método são resolvidas em tempo de compilação, eliminando a sobrecarga de funções virtuais. Você pode combinar vários mixins CRTP para compor funcionalidades, tornando essa uma alternativa flexível ao polimorfismo em tempo de execução quando os tipos são conhecidos em tempo de compilação.

challenge icon

Desafio

Fácil

Vamos criar um sistema de registro que usa mixins CRTP para adicionar funcionalidades reutilizáveis a diferentes classes sem o custo adicional de funções virtuais. Você criará dois mixins que podem ser “misturados” em qualquer classe: um para contar instâncias e outro para gerar representações em string.

Você organizará seu código em três arquivos:

  • Mixins.h: Defina dois templates de mixin CRTP que fornecem funcionalidades reutilizáveis.

    Crie um template de mixin Countable que acompanhe quantas instâncias de uma classe derivada existem. Ele deve ter um contador estático que seja incrementado no construtor e decrementado no destrutor. Forneça um método estático getCount() que retorne a contagem atual.

    Crie um template de mixin Describable que forneça um método describe(). Esse método deve usar static_cast para acessar a classe derivada e chamar seu método getDescription(), então imprimir o resultado seguido por uma nova linha.

    Lembre-se de que os mixins CRTP usam static_cast<Derived*>(this) para acessar os membros da classe derivada em tempo de compilação.

  • Entities.h: Defina duas classes de entidade que herdem de ambos os mixins.

    Crie uma classe Player que herde de Countable<Player> e Describable<Player>. Ela deve armazenar um nome e um nível, além de fornecer um método getDescription() que retorne uma string no formato: Player: [name] (Level [level])

    Crie uma classe Enemy que também herde de ambos os mixins. Ela deve armazenar um tipo e uma quantidade de vida, além de fornecer um método getDescription() que retorne: Enemy: [type] with [health] HP

    Não se esqueça de inicializar o contador estático para cada classe.

  • main.cpp: Leia quatro entradas (cada uma em uma linha separada):
    1. Nome do Player (string)
    2. Nível do Player (inteiro)
    3. Tipo do Enemy (string)
    4. Vida do Enemy (inteiro)

    Crie um Player e um Enemy com os valores fornecidos. Em seguida, demonstre os mixins:

    1. Imprima Player count: [count] usando o método estático getCount()
    2. Imprima Enemy count: [count]
    3. Chame describe() no player
    4. Chame describe() no enemy
    5. Crie um segundo player com o nome "Guest" e o nível 1
    6. Imprima Player count: [count] novamente para mostrar a contagem atualizada
    7. Chame describe() no segundo player

Por exemplo, com as entradas 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)

Este desafio demonstra como os mixins CRTP adicionam funcionalidades (contagem e descrição) a classes não relacionadas sem usar funções virtuais. Tanto Player quanto Enemy obtêm as mesmas capacidades ao herdar dos mesmos templates de mixin, mas cada um mantém seu próprio contador de instâncias separado, pois o template é instanciado com tipos diferentes.

Experimente você mesmo

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

using namespace std;

int main() {
    // Ler as entradas
    string playerName;
    int playerLevel;
    string enemyType;
    int enemyHealth;
    
    cin >> playerName;
    cin >> playerLevel;
    cin >> enemyType;
    cin >> enemyHealth;
    
    // TODO: Criar um Player com o nome e nível fornecidos
    
    // TODO: Criar um Enemy com o tipo e vida fornecidos
    
    // TODO: Imprimir "Player count: [count]" usando Player::getCount()
    
    // TODO: Imprimir "Enemy count: [count]" usando Enemy::getCount()
    
    // TODO: Chamar describe() no player
    
    // TODO: Chamar describe() no enemy
    
    // TODO: Criar um segundo player com o nome "Guest" e nível 1
    
    // TODO: Imprimir "Player count: [count]" novamente
    
    // TODO: Chamar describe() no segundo player
    
    return 0;
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos

Pratique por conta própria: Compilador de C++ online