Saltar a contenido

Ú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:

template <class Super>
class Mixin : public Super {
. /* mixin body */
};

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:

using ModifiedShape = Border<RotateBy90<Scale<Square, 2, 1>>, 10>;

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:

Name ned("Eddard", "Stark");    
repeatPrint(ned).repeat(10);

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.

template <typename _Except>
struct _Nested_exception : public _Except, public nested_exception
{}

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

Comentarios