この節では、テンプレートを使用する場合の問題について説明しています。
テンプレート定義で使用される名前の中には、テンプレート引数によって、またはそのテンプレート内で、定義されていないものがある可能性があります。そのような場合にはコンパイラが、定義の時点で、またはインスタンス化の時点で、テンプレートを取り囲むスコープから名前を解決します。1 つの名前が複数の場所で異なる意味を持つために解決の形式が異なることも考えられます。
名前の解決は複雑です。したがって、汎用性の高い標準的な環境で提供されているもの以外は、非局所型名前に依存することは避ける必要があります。言い換えれば、どこでも同じように宣言され、定義されている非局所型名前だけを使用するようにしてください。この例では、テンプレート関数の converter が、非局所型名前である intermediary と temporary を使用しています。これらの名前は use1.cc と use2.cc では異なる定義を持っているため、コンパイラが異なれば結果は違うものになるでしょう。テンプレートが正しく機能するためには、すべての非局所型名前 (intermediary と temporary) がどこでも同じ定義を持つ必要があります。
use_common.h
// Common template definition
template <class Source, class Target>
Target converter(Source source)
{temporary = (intermediary)source;
return (Target)temporary;}
use1.cc
typedef int intermediary;
int temporary;
#include "use_common.h"
use2.cc
typedef double intermediary;
unsigned int temporary;
#include "use_common.h"
非局所型名前を使用する典型的な例として、1 つのテンプレート内で cin と cout のストリームの使用があります。ほとんどのプログラマは実際、ストリームをテンプレートパラメータとして渡すことは望まないので、1 つの大域変数を参照するようにします。しかし、cin と cout はどこでも同じ定義を持っている必要があります。
テンプレートインスタンス化の際には、型と名前が一致することを目安に、どのテンプレートがインスタンス化または再インスタンス化される必要があるか決定されます。したがって、局所型がテンプレート引数として使用された場合には重大な問題が発生する可能性があります。自分のコードに同様の問題が生じないように注意してください。次に例を示します。
例 6-1 テンプレート引数としての局所型の問題の例
array.h
template <class Type> class Array {
Type* data;
int size;
public:
Array(int sz);
int GetSize();
};
array.cc
template <class Type> Array<Type>::Array(int sz)
{size = sz; data = new Type[size];}
template <class Type> int Array<Type>::GetSize()
{return size;}
file1.cc
#include "array.h"
struct Foo {int data;};
Array<Foo> File1Data(10);
file2.cc
#include "array.h"
struct Foo {double data;};
Array<Foo> File2Data(20);
file1.cc に登録された Foo 型は、file2.cc に登録された Foo 型と同じではありません。局所型をこのように使用すると、エラーと予期しない結果が発生することがあります。
テンプレートは、使用前に宣言されている必要があります。フレンド宣言では、テンプレートを宣言するのではなく、テンプレートの使用を宣言します。フレンド宣言の前に、実際のテンプレートが宣言されている必要があります。次の例では、作成済みオブジェクトファイルをリンクしようとするときに、operator<< 関数が未定義であるというエラーが生成されます。その結果、operator<< 関数はインスタンス化されません。
例 6-2 フレンド宣言の問題の例
array.h
// generates undefined error for the operator<< function
#ifndef ARRAY_H
#define ARRAY_H
#include <iosfwd>
template<class T> class array {
int size;
public:
array();
friend std::ostream&
operator<<(std::ostream&, const array<T>&);
};
#endif
array.cc
#include <stdlib.h>
#include <iostream>
template<class T> array<T>::array() {size = 1024;}
template<class T>
std::ostream&
operator<<(std::ostream& out, const array<T>& rhs)
{return out <<’[’ << rhs.size <<’]’;}
main.cc
#include <iostream>
#include "array.h"
int main()
{
std::cout
<< "creating an array of int... " << std::flush;
array<int> foo;
std::cout << "done\n";
std::cout << foo << std::endl;
return 0;
}
コンパイラは、次の宣言を array クラスの friend である正規関数の宣言として読み取っているので、コンパイル中にエラーメッセージを表示しません。
friend ostream& operator<<(ostream&, const array<T>&);
operator<< は実際にはテンプレート関数であるため、template class array を宣言する前にこの関数にテンプレート宣言を行う必要があります。しかし、operator<< はパラメータ type array<T> を持つため、関数宣言の前に array<T> を宣言する必要があります。ファイル array.h は、次のようになります。
#ifndef ARRAY_H
#define ARRAY_H
#include <iosfwd>
// the next two lines declare operator<< as a template function
template<class T> class array;
template<class T>
std::ostream& operator<<(std::ostream&, const array<T>&);
template<class T> class array {
int size;
public:
array();
friend std::ostream&
operator<< <T> (std::ostream&, const array<T>&);
};
#endif
C++ 標準は、テンプレート引数に依存する修飾名を持つ型を、typename キーワードを使用して型名として明示的に示すことを規定しています。これは、それが型であることをコンパイラが認識できる場合も同様です。次の例の各コメントは、それぞれの修飾名が typename キーワードを必要とするかどうかを示しています。
struct simple {
typedef int a_type;
static int a_datum;
};
int simple::a_datum = 0; // not a type
template <class T> struct parametric {
typedef T a_type;
static T a_datum;
};
template <class T> T parametric<T>::a_datum = 0; // not a type
template <class T> struct example {
static typename T::a_type variable1; // dependent
static typename parametric<T>::a_type variable2; // dependent
static simple::a_type variable3; // not dependent
};
template <class T> typename T::a_type // dependent
example<T>::variable1 = 0; // not a type
template <class T> typename parametric<T>::a_type // dependent
example<T>::variable2 = 0; // not a type
template <class T> simple::a_type // not dependent
example<T>::variable3 = 0; // not a type
「>>」 という文字連続型は右シフト演算子と解釈されるため、あるテンプレート名を別のテンプレート名で使用する場合は注意が必要です。隣接する「>」文字を少なくとも 1 つの空白文字で区切ってください。
次に誤った書式の例を示します。
Array<String<10>> short_string_array(100); // >> = right-shift
前述の文は、次のように解釈されます。
Array<String<10 >> short_string_array(100);
正しい構文は次のとおりです。
Array<String<10> > short_string_array(100);
テンプレート定義の内部では、大域スコープや名前空間で静的として宣言されたオブジェクトや関数の参照がサポートされません。複数のインスタンスが生成されると、それぞれのインスタンスが別々のオブジェクトを参照するため、一定義規約 (C++ 標準の第 3.2 節) に違反するためです。通常、このエラーはリンク時にシンボルの不足の形で通知されます。
すべてのテンプレートのインスタンス化で同じオブジェクトを共有する場合は、そのオブジェクトを該当する名前空間の非静的メンバーにします。また、あるテンプレートクラスをインスタンス化するたびに、別々のオブジェクトを使用する場合は、そのオブジェクトを該当するテンプレートクラスの静的メンバーにします。同様に、あるテンプレート関数をインスタンス化するたびに、別々のオブジェクトを使用する場合は、そのオブジェクトを該当するテンプレート関数の局所メンバーにします。
-instances=extern を指定して複数のプログラムまたはライブラリを構築する場合は、それらを別のディレクトリに構築することを推奨します。同一ディレクトリ内に構築する場合は、構築ごとにリポジトリを消去する必要があります。これにより、予期しないエラーが回避されます。詳細は、「7.4.4 テンプレートリポジトリの共有」を参照してください。
次のような各ファイルが存在する、次の例を考慮してください。make ファイル、 a.cc、b.cc、x.h、および x.cc この例は、-instances=extern を指定する場合にのみ意味があることに注意してください。
........
Makefile
........
CCC = CC
all: a b
a:
$(CCC) -I. -instances=extern -c a.cc
$(CCC) -instances=extern -o a a.o
b:
$(CCC) -I. -instances=extern -c b.cc
$(CCC) -instances=extern -o b b.o
clean:
/bin/rm -rf SunWS_cache *.o a b...
x.h
...
template <class T> class X {
public:
int open();
int create();
static int variable;
};...
x.cc
...
template <class T> int X<T>::create() {
return variable;
}
template <class T> int X<T>::open() {
return variable;
}
template <class T> int X<T>::variable = 1;...
a.cc
...
#include "x.h"
int main()
{
X<int> temp1;
temp1.open();
temp1.create();
}...
b.cc
...
#include "x.h"
int main()
{
X<int> temp1;
temp1.create();
}
a と b の両方を構築する場合は、それらの構築の間に make clean を実行します。次のコマンドでは、エラーが発生します。
example% make a example% make b
次のコマンドでは、エラーは発生しません。
example% make a example% make clean example% make b