Menu
CoddyTech

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 modelo, passando a si mesma como argumento do modelo. Isso possibilita o polimorfismo em tempo de compilação e permite que classes base acessem membros da classe derivada sem funções virtuais.

A estrutura básica de 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";
    }
};

O CRTP é particularmente poderoso para criar mixins — funcionalidades reutilizáveis que podem ser “misturadas” à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 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 rastreie quantas instâncias de uma classe derivada existem. Ele deve ter um contador estático que é 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(), depois imprimir o resultado seguido por uma quebra de linha.

    Lembre-se de que 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 herdam 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, e 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, e fornecer um método getDescription() que retorne: Enemy: [type] with [health] HP

    Não se esqueça de inicializar o contador estático de 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() para o player
    4. Chame describe() para o enemy
    5. Crie um segundo player com o nome "Guest" e nível 1
    6. Imprima Player count: [count] novamente para mostrar a contagem atualizada
    7. Chame describe() para o 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 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() {
    // Leia as entradas
    string playerName;
    int playerLevel;
    string enemyType;
    int enemyHealth;
    
    cin >> playerName;
    cin >> playerLevel;
    cin >> enemyType;
    cin >> enemyHealth;
    
    // TODO: Crie um Player com o nome e o nível fornecidos
    
    // TODO: Crie um Enemy com o tipo e a vida fornecidos
    
    // TODO: Imprima "Player count: [count]" usando Player::getCount()
    
    // TODO: Imprima "Enemy count: [count]" usando Enemy::getCount()
    
    // TODO: Chame describe() para o jogador
    
    // TODO: Chame describe() para o inimigo
    
    // TODO: Crie um segundo jogador com o nome "Guest" e nível 1
    
    // TODO: Imprima "Player count: [count]" novamente
    
    // TODO: Chame describe() para o segundo jogador
    
    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