前回の投稿 Recursion, List Manipulation, and Lazy Evaluation では、関数型プログラミングの特徴について書きました:C++ における遅延評価の話は短いです。申し訳ありませんが、テンプレートを忘れてしまいました。 CRTP と式テンプレートの 2 つの高度な手法は、遅延評価に基づいています。
CRTP
しかし、CRTP とはどういう意味ですか?頭字語 CRTP は、C++ のイディオム C を表します。 不思議にR 繰り返し T テンプレート P attern とは、クラス Derived がクラス テンプレート Base から派生する C++ の手法を意味します。重要なのは、Base がテンプレート引数として Derived を持っていることです。
template<class T> class Base{ ... }; class Derived : public Base<Derived>{ ... };
それが衝撃的ではない場合、遅延評価はどのように開始されますか?最初の遅延評価。
できるだけ怠け者
CRTP イディオムを理解するための重要な観察事項は、クラス テンプレートのメソッドのインスタンス化は必要な場合にのみ発生するということです。証明?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // lazy.cpp #include <iostream> template<class T> struct Lazy{ void func() { std::cout << "func" << std::endl;} void func2(); // not defined }; int main(){ std::cout << std::endl; Lazy<int> lazy; lazy.func(); std::cout << std::endl; } |
クラスのメソッド func2 (8行目) Lazy は宣言されているだけで定義されていませんが、コンパイラはプログラムを受け入れます。 func2 を呼び出さないので、定義は必要ありません。
クラス テンプレートのメソッドの定義は、呼び出された場合にのみ必要になるため、これはまさに CRTP が使用するプロパティです。基本クラスのインスタンス化には、メソッドの宣言で十分です。したがって、静的ポリモーフィズムを実装できます。
静的ポリモーフィズム
静的ポリモーフィズムは、動的ポリモーフィズムに非常に似ています。しかし、仮想メソッドによる動的ポリモーフィズムとは対照的に、メソッド呼び出しのディスパッチはコンパイル時に行われます。今、私たちは CRTP イディオムの中心にいます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | // crtp.cpp #include <iostream> template <typename Derived> struct Base{ void interface(){ static_cast<Derived*>(this)->implementation(); } void implementation(){ std::cout << "Implementation Base" << std::endl; } }; struct Derived1: Base<Derived1>{ void implementation(){ std::cout << "Implementation Derived1" << std::endl; } }; struct Derived2: Base<Derived2>{ void implementation(){ std::cout << "Implementation Derived2" << std::endl; } }; struct Derived3: Base<Derived3>{}; template <typename T> void execute(T& base){ base.interface(); } int main(){ std::cout << std::endl; Derived1 d1; execute(d1); Derived2 d2; execute(d2); Derived3 d3; execute(d3); std::cout << std::endl; } |
関数テンプレートの実行で静的ポリモーフィズムを使用します (29 ~ 32 行目)。各引数ベースで base.interface メソッドを呼び出します。 7 行目から 9 行目のメソッド Base::interface は、CRTP イディオムの重要なポイントです。メソッドは、派生クラスの実装にディスパッチします :static_cast
これがプログラムの出力です。
確かに、この例の唯一の目的は、静的ポリモーフィズムの背後にあるメカニズムを紹介することでした。説得力のある例はまだありません。ここにいます。
CRTP とのミックスイン
ミックスインは、新しいコードを混在させるクラスの設計において一般的な概念です。したがって、複数の継承を使用してクラスの動作を変更することは、Python でよく使用される手法です。 C++ とは対照的に、Python では、クラス階層内にメソッドの定義を複数持つことは合法です。 Python は、メソッド解決順序 (MRO) の最初のメソッドを単純に使用します。
CRTP を使用して、C++ でミックスインを実装できます。顕著な例はクラス std::enable_shared_from_this です。このクラスを使用すると、std::shared_ptr を自分自身に返すオブジェクトを作成できます。クラス MySharedClass public を std::enable_shared_from_this から派生させる必要があります。これで、クラス MySharedClass には、そのオブジェクトに std::shared_ptr を作成するためのメソッド shared_from_this があります。 std::enable_shared_from_this の詳細については、私の投稿 std::shared_ptr の専門分野で読むことができます。
mixin の追加の典型的な使用例は、そのインスタンスが等値と不等値の比較をサポートする機能で拡張したいクラスです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | // crtpEquality.cpp #include <iostream> #include <string> template<class Derived> class Equality{}; template <class Derived> bool operator == (Equality<Derived> const& op1, Equality<Derived> const & op2){ Derived const& d1 = static_cast<Derived const&>(op1); Derived const& d2 = static_cast<Derived const&>(op2); return !(d1 < d2) && !(d2 < d1); } template <class Derived> bool operator != (Equality<Derived> const& op1, Equality<Derived> const & op2){ Derived const& d1 = static_cast<Derived const&>(op1); Derived const& d2 = static_cast<Derived const&>(op2); return !(op1 == op2); } struct Apple:public Equality<Apple>{ Apple(int s): size{s}{}; int size; }; bool operator < (Apple const& a1, Apple const& a2){ return a1.size < a2.size; } struct Man:public Equality<Man>{ Man(std::string n): name{n}{} std::string name; }; bool operator < (Man const& m1, Man const& m2){ return m1.name < m2.name; } int main(){ std::cout << std::boolalpha << std::endl; Apple apple1{5}; Apple apple2{10}; std::cout << "apple1 == apple2: " << (apple1 == apple2) << std::endl; Man man1{"grimm"}; Man man2{"jaud"}; std::cout << "man1 != man2: " << (man1 != man2) << std::endl; std::cout << std::endl; } |
クラス Apple と Man に対して小さい演算子を実装しました (28 行目と 37 行目)。さらなる理由として、簡単にするためにクラス Man のみを使用します。クラス Man はクラス Equality
これで、Apple と Man の平等と不平等を比較できます。
次は?
CRTP に加えて、式テンプレートも遅延評価の考え方に基づいています。式テンプレートは「コンパイル時の計算を表す構造であり、その構造は必要に応じてのみ評価されます」 計算全体の効率的なコードを生成する" (https://en.wikipedia.org/wiki/Expression_templates). 必要に応じて、それが遅延評価のポイントであるため、式テンプレートは次の投稿のトピックです.