第38章 クラステンプレート2

 今回は前回のソースを見て、どこがどうなっているのかを解説したいと思います。とは言っても、どうなっているかほとんど予想が付いている人もいるかもしれませんが。


 それでは、今回の要点です。


 では、いってみましょう。


 今やることの確認です。CIntArray は int 型の配列しか使えなかったので、これをどの型の配列でも作れるようにしたいのでした。これにはクラステンプレートが利用できます。

 クラステンプレートはクラスのテンプレートです。一部の型を自由に決めることができるわけです。

 関数テンプレートとクラステンプレートは同じ「テンプレート」なので、そう文法が違うわけではありません

 関数テンプレートでは関数の前に template <typename TYPE> のように書くのでした。これはクラステンプレートでも同じです。

CIntArray
class CIntArray
{
   ...
CArray
template <typename TYPE> class CArray
{
   ...

 そして、int の配列を扱う部分を TYPE 用に書き換えてやればいいわけです。

CIntArray
    ....
    int* m_pnum;
    ...
    int Get(const int index) const;
    ...
    operator const int*() const;  // 配列の直接参照
    ...
CArray
    ....
    TYPE* m_pnum;
    ...
    TYPE Get(const int index) const;
    ...
    operator const TYPE*() const;  // 配列の直接参照
    ...

 ここまでは何も問題ありませんね。


 次に、メンバ関数の実装は無視して、クラステンプレートを使う部分を見てみましょう。

CArray <char> str2(sizeof str1);

 明示的にテンプレート引数を指定していますね。このようにクラステンプレートではテンプレート引数は必ず明示的に指定します。関数テンプレートと違い自動的に判断できる状況が少ないので、ややこしいので初めから自動的に判断できなくなっているわけです。

 このことは後々話すことにも影響してきます。クラステンプレートの文法を考えるとき、このことを意識すれば大抵説明がつきます。


 さて、問題のメンバ関数の実装です。

CIntArrayCArray
// メンバの初期化
void CArray::Init()
{
    m_pnum   = NULL;
    m_nNumOf = 0;
}
// メンバの初期化
template <typename TYPE>
void CArray <TYPE> ::Init()
{
    m_pnum   = NULL;
    m_nNumOf = 0;
}

 先ず、メンバ関数の実装にも template <typename TYPE> が付いています。そして、それだけではなくクラス名を書く部分に <TYPE> というのが付いています

 ああ、そういう文法なんだと済ませてもいいのですが、ちょっとどうなっているのか考えてみましょう。

 普通のメンバ関数、例えば CIntArray クラスのメンバ関数を実装するときは

void CIntArray::Init()

のように、関数名の前に「<クラス名>::」を書くのでした。

 ああそうなんだと

void CArray::Init()

とするだけでは、エラーになります。

 CArray は「クラステンプレート」であって「クラス」ではないからです。クラステンプレートは「実体化」して初めて「クラス」になるわけです。

 ということは、CArray だけではなく、テンプレート引数も指定しないといけないわけです。クラステンプレートは常にテンプレート引数を必要とする、ということがここで効いてきます。しかし、

void CArray <int> ::Init()

のように各実体に対して実装してはテンプレートの意味がありません。そこで、ここでテンプレートを利用して、

template <typename TYPE>
void CArray <TYPE> ::Init()

とするわけです。このように考えれば、この文法もすっきり理解できると思います。

 そして、このことを踏まえればクラステンプレートのメンバ関数の特殊化

template < >
void CArray <char> ::Init()

とできることも分かります。

 注意することは、この理解の方法が厳密に正しいかどうかは自分も知らないということです。あくまで予測に基づく「解釈」や「こじつけ」でしかないということを言っておきます。実際、「template <TYPE> を付けたが、それだけではその TYPE がどういう意味を持っているか分からないので CArray <TYPE> と明示的に指定している」と解釈しても構いません。

 どちらにしろ、そういう文法にしないとコンパイラが解釈しづらいということです。(C/C++は特殊な文法をできるだけ増やさないように作ってある感じがしますね。)


 では、次に進みます。次はクラステンプレートを引数にとる関数についてです。

 これもクラステンプレートは常にテンプレート引数が必要だということを踏まえれば簡単に分かります。

 先ず、CIntArray では

CIntArray::CIntArray(const CIntArray& rother)

となっていました。これをそのまま

CArray::CArray(const CArray& rother)

とすることはできません。先ず、クラステンプレートのメンバ関数なので

template <TYPE>
CArray <TYPE> ::CArray(const CArray& rother)

となります。そして、引数にある CArray にもテンプレート引数を指定してやる必要があります。この場合は同じテンプレート引数で実体化されたクラスへの参照を受け取りたいので、TYPE をそのまま指定してやればいいことになります。

template <TYPE>
CArray <TYPE> ::CArray(const CArray <TYPE> & rother)

これで完了です。

 このことを踏まえれば、任意テンプレート引数で実体化されたクラステンプレートを受け付ける関数テンプレート

template <TYPE>
void Func(const CArray <TYPE> & rarray);

や、その特殊化

template < >
void Func(const CArray <char> & rarray);

そして、特定のテンプレート引数で実体化されたクラスのみを受け付けるような関数

void Func2(const CArray <char> & rarray);

を作ることも簡単です。


 今回はこれで終わりです。え? CArray の残り? しょうがないですね。ここに CArray のソースを全部載せておきます。

 では、今回の要点です。


 それでは、次回まで。


第37章 クラステンプレート | 第39章 クラステンプレート3

Last update was done on 2000.12.7

この講座の著作権はロベールが保有しています