Última actualización:2026-10-12
C++ Idioma mixin¶
Hola a todos, soy 光城. Hoy comparto con vosotros uno de los idiomas de C++那些事: mixin
Los mixins son un concepto procedente de Lisp. Un mixin es una parte de una clase, lo que significa que está pensado para combinarse con otras clases u otros mixins. La diferencia entre una clase independiente convencional (por ejemplo Person) y un mixin es que el mixin modela pequeños fragmentos de funcionalidad (por ejemplo imprimir o mostrar) y no está pensado para usarse por sí solo; al contrario, debe combinarse con otras clases que necesiten esa funcionalidad (por ejemplo Person).
Por tanto, el propósito de los mixins es permitir algo parecido a la herencia múltiple.
La escritura básica de un mixin en C++ es la siguiente:
o bien
template<typename... Super>
class Mixin : public Super... {
public:
Mixin() : Super...() {}
// ...
};
Nota: todo el código de ejemplo de esta sección se puede obtener en el Planeta de Conocimiento.
Ejemplo 1: escalar y rotar¶
Supongamos que queremos escalar, rotar o añadir un borde a un rectángulo o un cuadrado; todas estas operaciones afectan a su anchura y altura, y podemos usar mixins para implementarlas.
Primero podemos definir el cuadrado y el rectángulo normales como clases base del 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;
};
Después usamos subclases mixin para extender la funcionalidad; por ejemplo: para un mixin de escalado podemos definir nuestra propia clase, cuya anchura y altura equivalgan a scale multiplicado por la anchura y altura correspondientes.
// 缩放的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; }
};
Escenarios como este son perfectos para usar mixins.
Al usarlo:
Ejemplo 2: redo y undo¶
El ejemplo completo simula una operación de redo y undo, con la que podemos ir y venir entre los datos. Con Redoable<Undoable<Number>> podemos mezclar estas clases y usarlas juntas.
#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
}
Ejemplo 3: impresión repetida¶
Pongamos otro ejemplo de construcción con piezas: la impresión repetida.
Primero, una clase con la que podemos imprimir el nombre de una persona.
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_;
};
¿Y si quiero imprimirlo repetidamente?
Podemos hacerlo así, usando un mixin y pasándole la clase Name.
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:
Con esto se obtiene el resultado de impresión repetido n veces.
¿Y en qué escenarios encaja bien un mixin?
- Mantener la clase original sin cambios,
- El código cliente no usa directamente la clase original: necesita envolverla en el mixin para aprovechar la funcionalidad ampliada,
Biblioteca estándar¶
En la biblioteca estándar hay una técnica que usa mixins: std::nested_exception.
std::nested_exception es una clase mixin polimórfica capaz de capturar y almacenar la excepción actual, lo que permite anidar entre sí excepciones de cualquier tipo.
En el código fuente se ve que se usa nested_exception como clase base del mixin.
Referencias¶
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
Temas relacionados¶
- C++ Evitar el constructor por defecto | C++那些事
- C++ Idioma de la lista de inicialización de clases | C++那些事
- C++: plantillas de clase antes que plantillas de función | C++那些事
- C++ Idioma copy-and-swap | C++那些事
- C++ Inyección de dependencias | C++那些事
- C++ enum class enumeraciones fuertemente tipadas | C++那些事
- C++ Header-Only frente a separación de implementación | C++那些事
- C++ Idioma de evaluación perezosa | C++那些事
- C++ Idioma de la tabla de consulta | C++那些事
- C++ Punteros a miembros e información de tipos | C++那些事