ナビゲーションリンクをスキップ | |
印刷ビューの終了 | |
Oracle Solaris Studio 12.3: C++ ユーザーズガイド Oracle Solaris Studio 12.3 Information Library (日本語) |
この節では、テンプレートを使用する場合の問題について説明しています。
テンプレート定義で使用される名前の中には、テンプレート引数によって、またはそのテンプレート内で、定義されていないものがある可能性があります。そのような場合にはコンパイラが、定義の時点で、またはインスタンス化の時点で、テンプレートを取り囲むスコープから名前を解決します。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 テンプレートリポジトリの共有」を参照してください。
メイクファイル 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 の両方を構築する場合は、それらの 2 つの構築の間に make clean コマンドを追加します。次のコマンドでは、エラーが発生します。
example% make a example% make b
次のコマンドでは、エラーは発生しません。
example% make a example% make clean example% make b