2017-05-06 13 views
8

私はCRTPの周りを頭の中に入れようとしています。このフォーラムを含め、いくつかの良い情報源がありますが、私は静的多型の基本についていくらか混乱していると思います。以下のWikipediaのエントリを見て:CRTP静的多型についての混乱

template <class T> 
struct Base 
{ 
    void implementation() 
    { 
     // ... 
     static_cast<T*>(this)->implementation(); 
     // ... 
    } 

static void static_func() 
{ 
    // ... 
    T::static_sub_func(); 
     // ... 
    } 
}; 

struct Derived : public Base<Derived> 
{ 
    void implementation(); 
    static void static_sub_func(); 
}; 

は、私は、これはちょっとコンパイル時の仮想関数のように、()派生クラスでバリアント私は別の実装を持っているのに役立ちますことを理解しています。しかし、私の混乱は、私が原因ベースのテンプレートされることに、私は通常の継承と仮想関数と同じように、私は

void func(Base x){ 
    x.implementation(); 
} 

のような機能を持つことができないと思うが、私は

func(Derived x) 

を指定するかなければならないことですまたは

template<class T> 
func(T x) 

を使用するので、CRTPは、実際には、単に派生:: Baseの簡単な方法を実現する/シャドーイングとは反対に、この文脈で私を何を買うのか?複数の関数が含まれているときCRTPの

struct Base 
{ 
    void implementation(); 

struct Derived : public Base 
{ 
    void implementation(); 
    static void static_sub_func(); 
}; 
+0

議論されています。ポリモーフィックな振る舞い(静的または動的多態性を使用するかどうかに関わらず)を参照またはポインタで渡します。 –

+0

私のために働かない、変数を得るか、フィールド 'func'が宣言されたvoid void func(Base&x)... – user32849

+0

@ user32849 'Base'はテンプレートです。あなたは 'Base&'だけ使うことはできません。テンプレートの引数 'Base &'を入力する必要があります。これは 'func'もテンプレートでなければならないことを意味します。 – Angew

答えて

5

利点は明白になります。 C++の型システムの静的な性質、コールへと

[Live example]

struct Base 
{ 
    int algorithm(int x) 
    { 
    prologue(); 
    if (x > 42) 
     x = downsize(x); 
    x = crunch(x); 
    epilogue(); 
    return x; 
    } 

    void prologue() 
    {} 

    int downsize(int x) 
    { return x % 42; } 

    int crunch(int x) 
    { return -x; } 

    void epilogue() 
    {} 
}; 

struct Derived : Base 
{ 
    int downsize(int x) 
    { 
    while (x > 42) x /= 2; 
    return x; 
    } 

    void epilogue() 
    { std::cout << "We're done!\n"; } 
}; 

int main() 
{ 
    Derived d; 
    std::cout << d.algorithm(420); 
} 

この出力:(なしCRTPで)このコードを検討d.algorithmは、すべての機能をBaseから呼び出します。 Derivedでオーバーライドを試行した場合、呼び出されません。 CRTPを使用する場合

これが変更されます。

template <class Self> 
struct Base 
{ 
    Self& self() { return static_cast<Self&>(*this); } 

    int algorithm(int x) 
    { 
    self().prologue(); 
    if (x > 42) 
     x = self().downsize(x); 
    x = self().crunch(x); 
    self().epilogue(); 
    return x; 
    } 

    void prologue() 
    {} 

    int downsize(int x) 
    { return x % 42; } 

    int crunch(int x) 
    { return -x; } 

    void epilogue() 
    {} 
}; 

struct Derived : Base<Derived> 
{ 
    int downsize(int x) 
    { 
    while (x > 42) x /= 2; 
    return x; 
    } 

    void epilogue() 
    { std::cout << "We're done!\n"; } 
}; 

int main() 
{ 
    Derived d; 
    std::cout << d.algorithm(420); 
} 

出力:

を我々が行われています!
-26

[Live example]

この方法で、Baseでの実装は、実際Derivedが提供するたびDerivedに呼び出します "オーバーライドします。"

BaseがCRTPクラスでない場合、static_sub_funcへの呼び出しは決してDerived::static_sub_funcに解決されません。virtual機能対

  • CRTP:

    CRTPは、関連付けられた実行時のオーバーヘッドがありませんつまり、コンパイル時構築物である。他のアプローチを超えるCRTPの利点が何であるかについては


    基本クラス参照(通常は)を介して仮想関数を呼び出すには、関数へのポインタを介した呼び出しが必要であり、したがって間接費が発生し、インライン化が防止されます。

    基本クラスのコードの再利用:単にDerivedのすべてを実装する対

  • CRTP。

もちろん、CRTPは純粋にコンパイル時の構成です。コンパイル時の多態性を実現するには、コンパイル時に多態性の構文を使用する必要があります。

template <class T> 
int foo(Base<T> &actor) 
{ 
    return actor.algorithm(314); 
} 

template <class T> 
int bar(T &actor) 
{ 
    return actor.algorithm(314); 
} 

かつての対応より密接に多型をランタイムとより良い型の安全性を提供していますために、後者はベースよりダックタイピングである、:あなたがこれを行うことができる2つの方法があります。

+0

これは明らかにCRTPの大きな用途ですが、私の問題と一般にCRTPの「静的多型」の記述は、CRTPが多型と直接関係しないことです。実装に役立つツールです。 –

+0

@NirFriedman多態性と関係がありますが、*コンパイル時*多型です。テンプレートでのみ実現できます。私はこの点について答えに追加します。 – Angew

+0

多形性のコンパイル時間は何か分かります。そして、はい、あなたのコードにテンプレートクラスがあります。しかし、CRTPは、そのインタフェースに応じた型を使用する関数ではなく、多形性と同じです。ベースは、派生クラスまたは類似クラスの実装を支援する単なるツールです。実装の手助けをしているのであれば、実際には多態性に関するものではなく、インタフェースに関するものです。 –

6

"静的多型"としてのCRTPの説明は、CRPTが実際に使用されるものに関して、本当に有用でも正確でもありません。多相性は、実際には、同じインタフェースまたは契約を満たす異なるタイプを持つことに関するものです。それらの異なるタイプがそのインタフェースをどのように実装するかは、多形性と直交しています。ダイナミックな多型は、次のようになります。

Animal仮想 make_sound方法を提供する基本クラスをある
void foo(Animal& a) { a.make_sound(); } // could bark, meow, etc 

、そのDogCatなど、オーバーライド。静的多型です:

template <class T> 
void foo(T& a) { a.make_sound(); } 

これだけです。 fooの静的バージョンは、基本クラスから継承せずにmake_soundメソッドを定義するすべての型で呼び出すことができます。コールはコンパイル時に解決されます(つまり、vtableコールを支払うことはありません)。

CRTPはどこに収まるのですか? CRTPは実際にはインターフェースに関するものではないため、多形性に関するものではありません。 CRTPは物事をより簡単に実装できるようにすることです。 CRTPの魅力は、派生型が提供するすべてのことを十分に理解した上で、型のインターフェイスに直接物事を注入できることです。簡単な例は次のようになります。今、加算演算子を定義する任意のクラスは、またdouble方法を挙げることができる

template <class T> 
struct MakeDouble { 
    T double() { 
     auto& me = static_cast<T&>(*this); 
     return me + me; 
}; 

class Matrix : MakeDouble<Matrix> ... 

Matrix m; 
auto m2 = m.double(); 

CRTPを全て実装を助ける程度であり、インターフェイスはありません。だから、それはしばしば "静的多型"と呼ばれるという事実についてあまりにも心配しないでください。実際の標準的な例をCRTPが使用できるようにしたい場合は、Andrei AlexandrescuのModern C++デザインの第1章を参照してください。しかし、それを遅く取る:-)。

+0

CRTPは複数使用されています。これはその1つですが、質問者が話しているものも有効です。 –

0

あなたは

void func(Base x); 

または

void func(Derived x); 

どちらがあなたに静的ポリモーフィズムを与えることが正しいです。 Baseは型ではなく、2番目の型は多態型ではないため、最初はコンパイルされません。

ただし、2つの派生クラスDerived1Derived2があるとします。次に、funcそれ自体をテンプレートにすることができます。

template <typename T> 
void func(Base<T>& x); 

これは、その後Baseから派生した任意の型で呼び出すことができ、それが何であれ、パラメータの静的な型を呼び出すために機能するかを決定するために渡され使用されます。


これはCRTPの使い方の1つに過ぎず、私が推測していたのはあまり一般的ではないと言います。 Nir Friedmanは静的多型とは関係のない別の答えで示唆しているように使用することもできます。

両方の使用は、あなたが値によって `Base`インスタンスを取るときは、[オブジェクト・スライシング(https://en.wikipedia.org/wiki/Object_slicing)に苦しむ非常によくhere

関連する問題