Mixinの概念を理解しようとしていますが、それが何なのか理解できないようです。私が見る方法は、継承を使用してクラスの機能を拡張する方法であるということです。私は、人々がそれらを「抽象サブクラス」と呼ぶことを読みました。誰でもその理由を説明できますか?
次の例に基づいて回答を説明していただければ幸いです(私の講義スライドショーの1つから):
ミックスインとは何かを説明する前に、解決しようとしている問題を説明しておくと役立ちます。モデル化しようとしているアイデアや概念がたくさんあるとします。それらは何らかの形で関係しているかもしれませんが、大部分は直交しています。つまり、互いに独立して独立していることができます。ここで、継承を介してこれをモデル化し、これらの概念のそれぞれをいくつかの一般的なインターフェイスクラスから派生させることができます。次に、そのインターフェイスを実装する派生クラスで具象メソッドを提供します。
このアプローチの問題は、この設計では、これらの具体的なクラスのそれぞれを取得して結合するための明確で直感的な方法が提供されないことです。
ミックスインのアイデアは、それぞれが基本的な直交概念をモデリングし、必要な機能だけでより複雑なクラスを構成できるように、レゴのようなプリミティブクラスの束を提供することです。プリミティブクラス自体は、ビルディングブロックとして使用するためのものです。後で既存のクラスに影響を与えずに他のプリミティブクラスをコレクションに追加できるため、これは拡張可能です。
C++に戻って、これを行うための手法は、テンプレートと継承を使用することです。ここでの基本的な考え方は、テンプレートパラメーターを介して提供することにより、これらのビルディングブロックを接続することです。次に、それらを連鎖させます。 typedef
経由で、必要な機能を含む新しいタイプを作成します。
例を挙げて、やり直し機能を上に追加したいとします。以下のようになります。
#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'; // 84
mynum.undo();
cout << mynum.get() << '\n'; // 42
mynum.redo();
cout << mynum.get() << '\n'; // back to 84
}
オリジナルからいくつかの変更を加えたことがわかります。
value_type
を追加して、その使用を煩雑にしないようにしました。この方法では、ピースを貼り付けるたびに<foobar, int>
を入力し続ける必要はありません。typedef
が使用されます。これは、ミックスインのアイデアを説明するための簡単な例であることに注意してください。したがって、コーナーケースや面白い使用法は考慮されません。たとえば、数値を設定せずにundo
を実行すると、おそらく期待どおりに動作しません。
補足として、 この記事 も役立つかもしれません。
ミックスインは、通常、機能が必要とする基本機能を提供する指定されたクラスを通じて、別のクラスに機能を提供するように設計されたクラスです。たとえば、例を考えてみましょう。
この場合のミックスインは、値クラスの設定操作を取り消す機能を提供します。この可能性は、パラメーター化されたクラス(例ではNumber
クラス)によって提供されるget/set
機能に基づいています。
別の例( "MixinベースのC++プログラミング" から抽出):
template <class Graph>
class Counting: public Graph {
int nodes_visited, edges_visited;
public:
Counting() : nodes_visited(0), edges_visited(0), Graph() { }
node succ_node (node v) {
nodes_visited++;
return Graph::succ_node(v);
}
Edge succ_Edge (Edge e) {
edges_visited++;
return Graph::succ_Edge(e);
}
...
};
この例では、トラバース操作を実行するグラフクラスを指定すると、ミックスインは頂点のカウントの機能を提供します。
一般的に、C++のミックスインは [〜#〜] crtp [〜#〜] イディオムによって実装されます。このスレッドは、C++でのmixinの実装に関する良い読み物になる可能性があります。 C++ Mixin-Styleとは
CRTPイディオムを活用するmixinの例を次に示します(@Simpleに感謝)。
#include <cassert>
#ifndef NDEBUG
#include <typeinfo>
#endif
class shape
{
public:
shape* clone() const
{
shape* const p = do_clone();
assert(p && "do_clone must not return a null pointer");
assert(
typeid(*p) == typeid(*this)
&& "do_clone must return a pointer to an object of the same type"
);
return p;
}
private:
virtual shape* do_clone() const = 0;
};
template<class D>
class cloneable_shape : public shape
{
private:
virtual shape* do_clone() const
{
return new D(static_cast<D&>(*this));
}
};
class triangle : public cloneable_shape<triangle>
{
};
class square : public cloneable_shape<square>
{
};
このミックスインは、シェイプクラスのセット(階層)に異種コピーの機能を提供します。
Greatwolfからの回答は気に入っていますが、注意点が1つあります。
greatwolfは、「コンパイル時に合成クラスタイプが何であるかを正確に知っているため、ここでは仮想関数は本当に必要ありません。」と述べました。残念ながら、オブジェクトを多態的に使用すると、一貫性のない動作が発生する可能性があります。
彼の例からメイン関数を微調整してみましょう。
int main()
{
ReUndoableNumber mynum;
Undoable<Number>* myUndoableNumPtr = &mynum;
mynum.set(42); // Uses ReUndoableNumber::set
myUndoableNumPtr->set(84); // Uses Undoable<Number>::set (ReUndoableNumber::after not set!)
cout << mynum.get() << '\n'; // 84
mynum.undo();
cout << mynum.get() << '\n'; // 42
mynum.redo();
cout << mynum.get() << '\n'; // OOPS! Still 42!
}
「set」関数を仮想化することにより、適切なオーバーライドが呼び出され、上記の矛盾した動作は発生しません。
C++のミックスインは、 Curiously Recurring Template Pattern (CRTP)を使用して表現されます。 この投稿 は、他の再利用手法(コンパイル時のポリモーフィズム)よりも優れた機能を提供します。
これはインターフェースと同じように機能し、おそらく抽象的にも機能しますが、インターフェースを初めて取得する方が簡単です。
それは多くの問題に対処しますが、私が開発で見つけたものの多くは外部APIです。これを想像してください。
ユーザーのデータベースがあり、そのデータベースにはそのデータにアクセスする特定の方法があります。フェイスブックがあり、そのデータ(api)にアクセスする特定の方法があることを想像してください。
facebookやデータベースのデータを使用して、アプリケーションを実行する必要がある場合があります。そのため、「私を実装するものには必ず次のメソッドがあります」というインターフェイスを作成し、そのインターフェイスをアプリケーションに実装することができます...
インターフェイスは実装するリポジトリにメソッドが宣言されることを約束しているため、アプリケーションでそのインターフェイスを使用する場所や場所を問わず、データを切り替えると、常に定義しているメソッドがあり、したがって処理するデータ。
この動作パターンにはさらに多くのレイヤーがありますが、本質は、データまたはその他の永続アイテムがアプリケーションの大きな部分になるため、それが良いことであり、知らないうちに変更されると、アプリケーションが壊れる可能性があります:)
疑似コードを次に示します。
interface IUserRepository
{
User GetUser();
}
class DatabaseUserRepository : IUserRepository
{
public User GetUser()
{
// Implement code for database
}
}
class FacebookUserRepository : IUserRepository
{
public User GetUser()
{
// Implement code for facebook
}
}
class MyApplication
{
private User user;
MyApplication( IUserRepository repo )
{
user = repo;
}
}
// your application can now trust that user declared in private scope to your application, will have access to a GetUser method, because if it isn't the interface will flag an error.
概念を理解するために、クラスをしばらく忘れてください。 (最も人気のある)JavaScriptを考えてください。オブジェクトは、メソッドとプロパティの動的配列です。シンボルまたは文字列リテラルとしての名前で呼び出し可能。 2018年に標準C++でどのように実装しますか? 簡単ではありません。しかし、それがコンセプトの中核です。 JavaScriptでは、いつでも何でも好きなように追加および削除(別名ミックスイン)できます。非常に重要:クラスの継承はありません。
次に、C++に進みます。標準C++には必要なものがすべて揃っていますが、ここでの説明としては役立ちません。明らかに、C++を使用してミックスインを実装するためのスクリプト言語を作成しません。
はい、 これは良い記事です ですが、インスピレーションのみを目的としています。 CRTPは万能薬ではありません。また、いわゆるアカデミックアプローチは ここ であり、(本質的に)CRTPベースです。
この答えを投票する前に、おそらく p.o.c。ワンドボックスのコード :)を検討してください