Última atualização:2026-10-12
Idiom Mixin em C++¶
Olá a todos, eu sou 光城; hoje vou compartilhar com vocês um idiom do C++那些事: mixin
Mixins são um conceito de Lisp. Um mixin é uma parte de classe, o que significa que foi concebido para ser combinado com outras classes ou mixins.
A diferença entre uma classe independente convencional (por exemplo, Person) e um mixin é que o mixin modela pequenos fragmentos de funcionalidade (por exemplo, imprimir ou exibir) e não se destina ao uso isolado. Em vez disso, deve ser combinado com outras classes que precisem dessa funcionalidade (por exemplo, Person).
Assim, o objetivo dos mixins é permitir algo semelhante à herança múltipla.
Em C++, a forma básica de escrever um mixin é a seguinte:
Ou
template<typename... Super>
class Mixin : public Super... {
public:
Mixin() : Super...() {}
// ...
};
Nota: todo o código de exemplo desta seção pode ser obtido na comunidade.
Exemplo 1: escala e rotação¶
Suponha que queiramos redimensionar, rotacionar, acrescentar bordas etc. a um retângulo/quadrado — todas essas operações afetam a sua largura e altura; podemos usar mixin para implementá-las.
Primeiro, podemos definir o quadrado e o retângulo como classes base do mixin.
// 正方形类
class Square : public Shape {
public:
Square(int sideLength) : sideLength(sideLength) {}
int GetWidth() const override { return sideLength; }
int GetHeight() const override { return sideLength; }
private:
int sideLength;
};
// 长方形类
class Rectangle : public Shape {
public:
Rectangle(int width, int height) : width(width), height(height) {}
int GetWidth() const override { return width; }
int GetHeight() const override { return height; }
private:
int width;
int height;
};
Em seguida, usamos subclasses de mixin para estender a funcionalidade; por exemplo: para um mixin de escala, podemos definir uma classe cuja largura e altura equivalem a scale multiplicado pela largura e altura correspondentes.
// 缩放的Mixin
template <class Base, int ScaleX, int ScaleY>
class Scale : public Base {
public:
int GetScaledWidth() const { return Base::GetWidth() * ScaleX; }
int GetScaledHeight() const { return Base::GetHeight() * ScaleY; }
};
Cenários como este são muito adequados para o uso de mixin.
No uso:
Exemplo 2: redo e undo¶
O exemplo completo simula uma operação de redo e undo, permitindo-nos alternar entre os dados. Redoable<Undoable<Number>>: aqui podemos usar essas classes misturadas entre si.
#include <iostream>
using namespace std;
struct Number {
typedef int value_type;
int n;
void set(int v) {
n = v;
}
int get() const {
return n;
}
};
template <typename BASE, typename T = typename BASE::value_type>
struct Undoable : public BASE {
typedef T value_type;
T before;
void set(T v) {
before = BASE::get();
BASE::set(v);
}
void undo() {
BASE::set(before);
}
};
template <typename BASE, typename T = typename BASE::value_type>
struct Redoable : public BASE {
typedef T value_type;
T after;
void set(T v) {
after = v;
BASE::set(v);
}
void redo() {
BASE::set(after);
}
};
typedef Redoable<Undoable<Number>> ReUndoableNumber;
int main() {
ReUndoableNumber mynum;
mynum.set(42);
mynum.set(84);
cout << mynum.get() << '\n'; // Output: 84
mynum.undo();
cout << mynum.get() << '\n'; // Output: 42
mynum.redo();
cout << mynum.get() << '\n'; // Output: back to 84
}
Exemplo 3: impressão repetida¶
Mais um exemplo de montar blocos: impressão repetida.
Primeiro, há uma classe com a qual podemos imprimir o nome da pessoa.
class Name
{
public:
Name(std::string firstName, std::string lastName)
: firstName_(std::move(firstName))
, lastName_(std::move(lastName)) {}
void print() const
{
std::cout << lastName_ << ", " << firstName_ << '\n';
}
private:
std::string firstName_;
std::string lastName_;
};
E se eu quiser imprimir repetidamente?
Podemos fazer assim: usar mixin, passando a classe Name para dentro.
template<typename Printable>
struct RepeatPrint : Printable
{
explicit RepeatPrint(Printable const& printable) : Printable(printable) {}
void repeat(unsigned int n) const
{
while (n-- > 0)
{
this->print();
}
}
};
template<typename Printable>
RepeatPrint<Printable> repeatPrint(Printable const& printable)
{
return RepeatPrint<Printable>(printable);
}
Uso:
Assim, obtém-se o resultado da impressão repetida n vezes.
Então, para quais cenários o mixin é mais adequado?
- manter a classe original inalterada,
- o código do cliente não usa a classe original diretamente: precisa envolvê-la em um mixin para usar a funcionalidade ampliada,
Biblioteca padrão¶
Na biblioteca padrão há um uso da técnica de mixin: std::nested_exception.
std::nested_exception é uma classe mixin polimórfica que pode capturar e armazenar a exceção atual, permitindo aninhar exceções de tipos arbitrários entre si.
No código-fonte, vemos nested_exception usado como classe base do mixin.
Referências¶
https://cedanet.com.au/ceda/Xc++/$mixin/mixins-in-straight-c++.php
https://www.fluentcpp.com/2017/12/12/mixin-classes-yang-crtp/
https://stackoverflow.com/questions/18773367/what-are-mixins-as-a-concept
Tópicos relacionados¶
- C++ evitar o construtor padrão | C++那些事
- Idiom de lista de inicialização de classes em C++ | C++那些事
- C++: templates de classe em vez de templates de função | C++那些事
- Idiom copy-and-swap em C++ | C++那些事
- Injeção de dependência em C++ | C++那些事
- C++ enum class enumerações fortemente tipadas | C++那些事
- C++ Header-Only versus separação de implementação | C++那些事
- Idiom de avaliação preguiçosa em C++ | C++那些事
- Idiom de consulta em tabela em C++ | C++那些事
- Ponteiros para membros e type info em C++ | C++那些事