コンテナクラスとテンプレート(C++),Generics(Java)
オブジェクト指向で、純粋仮想基底クラスと、仮想関数を使った応用では、 コンテナクラスと呼ばれる方法が使われる。 コンテナクラスでは、空っぽの純粋仮想基底クラスを使って、データを便利に扱える 処理(アルゴリズム)を記述しておく。 自分でデータを扱うプログラムを作成する場合には、その空っぽクラスから派生させた クラスを定義し、仮想関数のメカニズムを使ってそのアルゴリズムが動くようにしておく。 こうすれば、面倒なアルゴリズムを、違う型でもそのアルゴリズムを使える。 このようなクラスは、コンテナクラスなどと呼ばれたり、Generic クラスなどと呼ばれる。
過去の記事では純粋仮想クラスの説明で、Object という純粋仮想基底クラスで、 データ並び替えなどのアルゴリズムが、使える例を示した。
しかし、この過去の記事では、配列には、Object から派生させた Integer や、CString が 入っている例で、便利に使える例として説明しているが、 一つの配列の中に、Integer や CString が混在したら、実は cmp() 関数で、 異なる型のことを考えていないため、実行時に動かなくなる。
こういった問題の解決として、色々なテクニックが考案されているが、この例が、 C++ のテンプレート機能と、Java の Generics 機能である。 ただ、この2つの機能は、文法の字面では似ているが、考え方はまるっきり違う。 以下に、固定長スタックを例に使い方を中心に説明する。
C++ テンプレート
C++のテンプレート機能の基本原理は、実はあんまりオブジェクト指向と関係はない。 例えば、整数データを記憶するスタックのプログラムは以下のようになる。
class Stack { private: int data[ 100 ] ; int sp ; public: Stack() { sp = 0 ; } void push( int x ) { data[ sp++ ] = x ; } int pop() { return data[ --sp ]; } } ; void main() { Stack s ; s.push( 1 ) ; s.push( 2 ) ; printf( "%d" , s.pop() ) ; printf( "%d" , s.pop() ) ; }
しかし、整数を覚えるスタックは記述できたが、実数を覚えるスタックや文字列を覚えるスタックは、改めて記述する必要がある。 この不便さを解消するのがテンプレート機能である。
template<class T> class Stack { private: T data[ 100 ] ; int sp ; public: Stack() { sp = 0 ; } void push( T x ) { data[ sp++ ] = x ; } T pop() { return data[ --sp ] ; } } ; void main() { Stack<int> si ; // 型Tにintを割り当てた、処理が生成される。 si.push( 1 ) ; printf( "%d" , si.pop() ) ; Stack<double> sd ; // 型Tにdoubleを割り当てた、処理が生成される。 sd.push( 1.23 ) ; printf( "%lf" , sd.pop() ) ; }
テンプレートの基本は、型情報を<class T>として雛形プログラムを記述しておき、 具体的に型を明記して使われた時点で、その型に応じた機械語を生成してくれる。
JavaのGenerics
JavaのGenericsは、字面や使い勝手では、C++のテンプレートと同じように使う。 しかし、実際は、Java の全データの純粋仮想基底クラスの Object 型を使っている。
public class Stack<T> { private T[] data ; private int sp ; public Stack() { sp = 0 ; data = (T[]) new Object[10] ; } public void push( T x ) { data[ sp++ ] = x ; } public T pop() { return data[ --sp ] ; } public static void main() { Stack<Integer> si ; // Integerはintのラッパークラス si.push( 1 ) ; System.out.println( si.pop() ) ; Stack<Double> sd ; // Doubleはdoubleのラッパークラス sd.push( 1.23 ) ; System.out.println( sd.pop() ) ; } }