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: AliceA 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.
Desafio
FácilVamos 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
Countableque 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áticogetCount()que retorne a contagem atual.Crie um template de mixin
Describableque forneça um métododescribe(). Esse método deve usarstatic_castpara acessar a classe derivada e chamar seu métodogetDescription(), 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
Playerque herde deCountable<Player>eDescribable<Player>. Ela deve armazenar um nome e um nível, além de fornecer um métodogetDescription()que retorne uma string no formato:Player: [name] (Level [level])Crie uma classe
Enemyque também herde de ambos os mixins. Ela deve armazenar um tipo e uma quantidade de vida, além de fornecer um métodogetDescription()que retorne:Enemy: [type] with [health] HPNão se esqueça de inicializar o contador estático para cada classe.
main.cpp: Leia quatro entradas (cada uma em uma linha separada):- Nome do Player (string)
- Nível do Player (inteiro)
- Tipo do Enemy (string)
- Vida do Enemy (inteiro)
Crie um Player e um Enemy com os valores fornecidos. Em seguida, demonstre os mixins:
- Imprima
Player count: [count]usando o método estáticogetCount() - Imprima
Enemy count: [count] - Chame
describe()no player - Chame
describe()no enemy - Crie um segundo player com o nome "Guest" e o nível 1
- Imprima
Player count: [count]novamente para mostrar a contagem atualizada - 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;
}
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
1Fundamentos de POO
Arquivos externosBuild e compilação em C++Arquivos de cabeçalho e arquivos-fonteNamespaces e escopoIntrodução à POO em C++Classes vs. objetosO ponteiro 'this'Métodos (funções membro)Atributos (membros de dados)Noções básicas de construtores e destruidoresRevisão - calculadora simples4Propriedades de Classe
Membros de Instância vs. EstáticosGetters e SettersFunções Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRevisão - Gerenciador de Contas Bancárias7Herança
Herança básicaNíveis de acesso da herançaOrdem de chamada de construtor e destrutorSobrescrita de métodosFunções virtuais e VTableHerança múltiplaHerança virtualRevisão - Hierarquia de funcionários10Visão geral da STL
Visão geral e filosofia da STLContêineres da STLIteradoresAlgoritmos da STLFunctors e expressões lambdaRecapitulação - Frequência de palavras13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonFactory e Abstract FactoryPadrão BuilderPadrão ObserverPadrão Strategy2Gerenciamento de Memória
Memória Stack vs. HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Ponteiros Inteligentes em C++RAII em C++Revisão - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de acesso em C++Especificadores de acesso em profundidadeOcultação de informaçõesStruct vs ClasseClasses aninhadas e internasRecapitulação - Sistema de registros de alunos8Polimorfismo
Polimorfismo em compilação vs. execuçãoSobrecarga de funçõesFunções virtuais revisadasFunções virtuais purasClasses abstratasDesign de interfaces em C++Conversão dinâmica e RTTIRecapitulação — Calculadora de formas11Conceitos Avançados de OOP
Composição vs. HerançaMixins via CRTPIdioma PimplApagamento de TiposClasses Enum e Tipagem ForteTratamento de Exceções em OOPHierarquias de Exceções Personalizadas14Padrões de Projeto — Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeRAII como Padrão3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentoListas de Inicialização do ConstrutorConstrutores DelegantesAprofundamento em DestrutoresRegra dos Três / Cinco / ZeroRevisão - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de FluxoSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçõesTemplates de ClassesEspecialização de TemplatesTemplates VariádicosNoções Básicas de SFINAE e Traits de TiposRecapitulação - Contêiner GenéricoPratique por conta própria: Compilador de C++ online