コンテンツにスキップ

最終更新:2026-10-12

C++ Mixin イディオム

皆さんこんにちは、光城です。今日はC++那些事の中から一つのイディオム、mixin を紹介します。

ミックスイン(Mixins)はLispの概念です。ミックスインはクラスの一部であり、他のクラスやミックスインと組み合わせて使われることを意図したものです。 通常の独立したクラス(例えばPerson)とミックスインの違いは、ミックスインはいくつかの小さな機能の断片(例えば印刷や表示)をモデル化するものであり、単体で使用されることを想定していない点です。むしろ、この機能を必要とする他のクラス(例えばPerson)と組み合わせられるべきものです。

したがって、ミックスインの目的は、多重継承に似たものを可能にすることにあります。

C++におけるmixinの基本的な書き方は次のとおりです:

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

または

template<typename... Super>
class Mixin : public Super... {
  public:
    Mixin() :  Super...() {}
  // ...
};

注:本節のすべてのサンプルコードは星球から入手できます。

例1:拡大縮小と回転

長方形や正方形に対して、拡大縮小、回転、枠線の追加などを行いたいとします。これらの操作はすべてその幅と高さに影響します。mixinを使ってこれを実現できます。

まず、正方形と長方形を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;
};

続いてmixinのサブクラスを使って機能を拡張します。例えば、拡大縮小のmixinとしては、幅と高さがscaleに対応する幅と高さを掛けたものに等しいクラスを自分で定義できます。

// 缩放的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; }
};

このようなシナリオはmixinを使うのに非常に適しています。

使用するときは次のようになります:

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

例2:redoとundo

完全な例ではredoとundoの操作をシミュレートし、データの間を行き来できるようにします。Redoable<Undoable<Number>>のように、ここではこれらのクラスを一緒にミックスして使うことができます。

#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
}

例3:繰り返し表示

もう一つ積み木を組み合わせるような例を挙げます。繰り返し表示(repeat print)です。

まず、人の名前をprintできるクラスがあります。

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_;
};

では、繰り返し表示したい場合はどうすればよいでしょうか?

このように遊ぶことができます。mixinを使って、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);
}

使用例:

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

このとき、n回繰り返された表示結果が得られます。

では、mixinはどのような場面に向いているのでしょうか?

  • 元のクラスを変更せずに保つ、
  • クライアントコードは元のクラスを直接使わず、mixinにラップして初めて拡張機能を使える、

標準ライブラリ

標準ライブラリの中にはmixin技術を使っているものがあります。std::nested_exception です。

std::nested_exception は多態的なmixinクラスであり、現在の例外を捕捉して保存することができ、それによって任意の型の例外を互いにネストできます。

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

ソースコードの中で、nested_exceptionがmixinの基底クラスとして使われているのが分かります。

参考

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

関連トピック

コメント