2017-12-08 31 views
1

クラスから継承するとき、コンパイラはそれを作成するために基本クラスの定義を知る必要があります。しかし、自分自身(継承クラス)を使ってテンプレートクラスを継承すると、コンパイラはどのようにコードを作成できますか?それはまだクラスのサイズを知らない。継承クラスを使用したテンプレートクラスからの継承

#include <iostream> 

template <class T> class IFoo 
{ 
public: 
    virtual T addX(T foo, double val) = 0; 
    // T memberVar; // uncomment for error 
}; 

class Foo : public IFoo<Foo> 
{ 
public: 
    Foo(double value) 
     : m_value(value) {} 

    Foo addX(Foo foo, double b) override 
    { 
     return Foo(foo.m_value + b); 
    } 

    double m_value; 
}; 

int main() 
{ 
    Foo foo1(1); 
    Foo foo2 = foo1.addX(foo1, 1); 

    std::cout << foo2.m_value; 
} 

最初はインターフェイスだから動作すると思っていましたが、通常のクラスでも動作します。

テンプレートをメンバーとして保存すると、期待どおりFooが定義されていないというエラーが表示されます。

+0

'memberVar'は' Foo'(間接的に)自身の別のインスタンスを保持します。本質的にそれは 'sizeof(Foo)== 2 * sizeof(Foo)'を作るでしょう。もちろん、動作しません。:-) –

+0

すてきな答えをありがとう。今私には明らかです。 – jaba

答えて

2

ここでの一般的な概念は、不思議経常テンプレートパターンまたはCRTPと呼ばれています。それを検索すると多くのヒットが得られます。 https://stackoverflow.com/questions/tagged/crtpを参照してください。

しかし、CRTPに多すぎることなく、あなたの質問に答えてくれるという簡単な説明があります。以下は、CやC++で許可されている:

struct foo { 
    struct foo *next; 
    ... 
}; 

または2種類:

struct foo; 
struct bar; 

struct foo { 
    struct bar *first; 
    ... 
}; 

struct bar { 
    struct foo *second; 
    ... 
}; 

限りstructまたはclassへのポインタのみが使用されているように、タイプの完全な定義は」doesnの利用可能でなければなりません。この上にテンプレートをさまざまな方法で重ねることができます。また、テンプレートをパラメータ化するタイプとそのテンプレート内での使用について別々に判断する必要があります。 SFINAE(代入失敗はエラーではない)に追加すると、与えられた型ではできないため、インスタンス化されないテンプレートを作成することもできます。

2

template class IFooのこの定義では、コンパイラはをレイアウトするためにFooのサイズを知る必要はありません。

Fooは、この文脈では不完全なクラスであり(未定義または未宣言ではない)、不完全な型を使用できる方法で使用できます。メンバ関数のパラメータリストに現れても問題ありません。メンバ変数をFoo*と宣言しても問題ありません。メンバ変数をFooと宣言することは禁じられています(完全型が必要です)。

1

コンパイラはどのようにコードを作成できますか?この質問に答える

は、この質問に答えると同じようになります:どのようにコンパイラがそれをコンパイルすることができますか?

struct Type; 
Type func(Type); 

Live example

どのように存在し、まだその型を使用する関数を宣言しないタイプを定義することができますか?

答えは簡単です:コンパイルするコードはありません。実際には存在しない型を使用します。コンパイルするコードがないので、どのように失敗することができますか?

あなたのコードに何が関係しているのでしょうか?どのようにして、クラスがその親にテンプレートパラメータとして自分自身を送ることができるのでしょうか?

のは、あなたがそれをやっている時に、コンパイラの参照が何を分析してみましょう:

struct Foo : IFoo<Foo> { /* ... */ }; 

まず、コンパイルがこれを見ている:

struct Foo ... 

コンパイラは今Fooが存在することを知っている、まだそれが不完全ですタイプ。それはIFooが何であるかを知っている、そしてそれはFooがタイプであることを知っている

... : IFoo<Foo> ... 

は今、彼はそれを見ています。コンパイラは今はそのタイプでIFooをインスタンス化する必要があります。

template <class T> struct IFoo 
{ 
    virtual T addX(T foo, double val) = 0; 
}; 

だから、本当に、それはそれで、関数の宣言で、クラスを宣言します。あなたは、不完全な型の関数が宣言されていることを確認しました。ここでも同じことが起こります。その時点で、このコードは次のようにあなたのコードが可能です:

struct Foo; 
template struct IFoo<Foo>; // instanciate IFoo with Foo 

本当にそこには魔法はありません。


ここで、もっと説得力のある例を考えてみましょう。そのことについて何?

template<typename T> 
struct IFoo { 
    void stuff(T f) { 
     f.something(); 
    } 
}; 

struct Foo : IFoo<Foo> { 
    void something() {} 
}; 

どのようにすることができ、不完全な形でのコンパイラの呼び出しsomething

事はありません。 somethingを使用するとFooが完了します。これは、テンプレート関数が使用されたときにのみインスタンス化されるためです。

テンプレートでも関数定義を分けることができますか?

template<typename T> 
struct IFoo { 
    void stuff(T f); 
}; 

template<typename T> 
void IFoo<T>::stuff(T f) { 
    f.something(); 
} 

struct Foo : IFoo<Foo> { 
    void something() {} 
}; 

素晴らしい!純粋な仮想関数であなたの例とまったく同じように見えるでしょうか?別の有効な変換を行いましょう:

template<typename T> 
struct IFoo { 
    void stuff(T f); 
}; 

struct Foo : IFoo<Foo> { 
    void something() {} 
}; 

// Later... 
template<typename T> 
void IFoo<T>::stuff(T f) { 
    f.something(); 
} 

完了! Fooが完了した後、関数を後で定義しました。そして、これは何が起こるのかです:コンパイラは使用されるときにのみIFoo<Foo>::stuffをinstanciateします。そしてそれが使用されるポイントは、Fooです。そこにも魔法はありません。


は、なぜあなたは、その後IFoo内部Tメンバ変数を宣言することはできませんか?

シンプル、同じ理由で、このコードはコンパイルされません理由:それは不完全な型の変数を宣言する意味がありません

struct Bar; 
Bar myBar; 

関連する問題