C++ ユーザーズガイド ホーム目次前ページへ次ページへ索引


第 4 章

テンプレートのコンパイル

テンプレートをコンパイルするためには、C++ コンパイラは従来の UNIX コンパイラよりも多くのことを行う必要があります。C++ コンパイラは、必要に応じてテンプレートインスタンスのオブジェクトコードを生成しなければなりません。コンパイラは、テンプレートレポジトリを使って、別々のコンパイル間でテンプレートインスタンスを共有することができます。また、テンプレートコンパイルのいくつかのオプションを使用できます。コンパイラは、別々のソースファイルにあるテンプレート定義を見つけ、テンプレートインスタンスと main コード行の整合性を維持しなければなりません。

冗長コンパイル

フラグ -verbose=template が指定されている場合は、テンプレートコンパイル作業中の重要なイベントがユーザーに通知されます。逆に、デフォルトの
-verbose=no%template が指定されている場合は、通知されません。そのほかに、
+w オプションを指定するとテンプレートインスタンス化が行われたときに問題になりそうな内容が通知される場合があります。

テンプレートコマンド

テンプレートレポジトリの管理は CCadmin(1) コマンドで行います。たとえば、プログラムの変更によって、インスタンス化が不要になり、記憶領域が無駄になることがあります。CCadmin -clean コマンド (以前のリリースの ptclean) を使用すれば、すべてのインスタンス化と関連データを整理できます。インスタンス化は、必要なときだけ再作成されます。

テンプレートインスタンスの配置とリンケージ

コンパイラには、インスタンスの配置とリンケージの方法として、外部、静的、大域、明示的、半明示的のどれを使うかを指定できます。

特別な理由がない限り、デフォルトの外部インスタンス方式を使用してください。詳細は、『C++ プログラミングガイド』を参照してください。

外部インスタンスリンケージ

外部インスタンスの場合では、すべてのインスタンスがテンプレートレポジトリ内に置かれます。テンプレートインスタンスは 1 つしか存在できません。つまり、インスタンスが未定義であるとか、重複して定義されているということはありません。テンプレートは必要な場合にのみ再インスタンス化されます。

テンプレートインスタンスは、レポジトリ内では大域リンケージを受け取ります。インスタンスは、外部リンケージで現在のコンパイル単位から参照されます。

外部リンケージは、-instances=extern オプションで指定します。このオプションはデフォルトです。

インスタンスはテンプレートレポジトリ内に保存されているので、外部インスタンスを使用する C++ オブジェクトをプログラムにリンクするには CC コマンドを使用しなければなりません。

使用するすべてのテンプレートインスタンスを含むライブラリを作成したい場合には、CC コマンドに -xar オプションを指定してください。ar コマンドは使用できません。次に例を示します。

example% CC -xar -o libmain.a a.o b.o c.o

詳細は、第 6 章を参照してください。

静的インスタンス

静的インスタンスの場合は、すべてのインスタンスが現在のコンパイル単位内に置かれます。その結果、テンプレートは各再コンパイル作業中に再インスタンス化されます。インスタンスはテンプレートレポジトリに保存されません。

インスタンスは静的リンケージを受け取ります。これらのインスタンスは、現在のコンパイル単位以外では認識することも使用することもできません。そのため、テンプレートの同じインスタンス化がいくつかのオブジェクトファイルに存在することがあります。これには、次の欠点があります。

静的インスタンスは潜在的にコンパイル速度が速いため、修正継続機能を使用したデバッグにも適しています (『dbx コマンドによるデバッグ』を参照してください)。

静的インスタンスリンケージは、-instances=static コンパイルオプションで指定します。

大域インスタンス

大域インスタンスの場合では、すべてのインスタンスが現在のコンパイル単位の中に置かれます。その結果、テンプレートは各再コンパイル作業中に再インスタンス化されます。テンプレートはテンプレートデータベースに保存されません。

テンプレートインスタンスは大域リンケージを受け取ります。これらのインスタンスは現在のコンパイル単位以外でも認識したり、使用したりできます。その結果、複数のコンパイル単位におけるインスタンス化でリンク作業中に複数のシンボル定義のエラーが生じることがあります。したがって、大域インスタンスは、インスタンスが繰り返されないことがわかっている場合に限り適しています。

大域インスタンスは、-instances=global オプションで指定します。

明示的インスタンス

明示的インスタンスの場合、インスタンスは、明示的にインスタンス化されたテンプレートに対してのみ生成されます。暗黙的なインスタンス化は行われません。インスタンスは現在のコンパイル単位内に置かれるため、テンプレートは再コンパイルのたびに再インスタンス化され、テンプレートレポジトリには保存されません。

テンプレートインスタンスは大域リンケージを受け取ります。これらのインスタンスは、現在のコンパイル単位の外でも認識でき、使用できます。同じプログラムで複数の明示的なインスタンス化があると、リンカーで複数シンボル定義エラーになります。したがって、明示的インスタンス方式は、明示的なインスタンス化でライブラリを構成する場合のように、インスタンスが繰り返されないことがわかっている場合に限り適しています。

明示的インスタンスは、-instances=explicit オプションで指定します。

半明示的インスタンス

半明示的インスタンスの場合、インスタンスは、明示的にインスタンス化されるテンプレートやテンプレート本体の中で暗黙的にインスタンス化されるテンプレートに対してのみ生成されます。main コード行内で行う暗黙的なインスタンス化は不完全になります。インスタンスは現在のコンパイル単位に置かれます。したがって、テンプレートは再コンパイルごとに再インスタンス化され、テンプレートレポジトリには保存されません。

明示的インスタンスは大域リンケージを受け取ります。これらのインスタンスは、現在のコンパイル単位の外でも認識でき、使用できます。同じプログラムで複数の明示的インスタンス化があると、リンカーで複数のシンボル定義エラーになります。したがって、半明示的インスタンスは、明示的なインスタンス化によってライブラリを構成する場合のように、明示的インスタンスが繰り返されないことがわかっている場合にだけ適しています。

明示的インスタンスの本体内から使用される暗黙的インスタンスは、静的リンケージを受け取ります。これらのインスタンスは現在のコンパイル単位の外では認識できません。そのため、テンプレートの同じインスタンス化がいくつかのオブジェクトファイルに存在することがあります。これには、次の 2 つの欠点があります。

半明示的インスタンスは、-instances=semiexplicit オプションで指定します。

テンプレートレポジトリ

テンプレートレポジトリには、別々のコンパイルで共有できるテンプレートインスタンスが格納されます。したがって、テンプレートインスタンスは必要なときだけコンパイルされます。テンプレートレポジトリには、外部インスタンスを使用したときにテンプレートのインスタンス化に必要な、ソースファイル以外のすべてのファイルが含まれています。このレポジトリはその他の種類のインスタンスには使用されません。

レポジトリの構造

テンプレートレポジトリは、デフォルトで、Sun WorkShop のキャッシュディレクトリ (SunWS_cache) にあります。Sun WorkShop のキャッシュディレクトリは、出力ファイルが置かれるのと同じディレクトリ内にあります。SUNWS_CACHE_DIR 環境変数を設定すれば、キャッシュディレクトリ名を変更できます。

テンプレートレポジトリへの書き込み

コンパイラは、テンプレートインスタンスを格納しなければならないとき、出力ファイルに対応するテンプレートレポジトリにそれらを保存します。たとえば、次の例では、オブジェクトファイルを ./sub/a.o に書き込み、テンプレートインスタンスを
./sub/SunWS_cache にあるレポジトリに書き込みます。

example% CC -o sub/a.o a.cc

コンパイラがテンプレートをインスタンス化するときにこのキャッシュディレクトリが存在しない場合は、このディレクトリが作成されます。

複数のテンプレートレポジトリからの読み取り

コンパイラは、読み取るオブジェクトファイルに対応したテンプレートレポジトリから読み取りを行います。たとえば次の例では、/sub1/SunWS_cache
./sub2/SunWS_cache から読み取り、必要に応じて ./SunWS_cache へ書き込みます。

example% CC sub1/a.o sub2/b.o

テンプレートレポジトリの共有

レポジトリ内にあるテンプレートは、ISO/ANSI C++ 標準の単一定義規則に違反してはなりません。つまり、テンプレートは、どの用途に使用される場合でも、1つのソースから派生したものでなければなりません。この規則に違反した場合の動作は定義されていません。この規則に違反しないようにするための (最も保守的で) 最も簡単な方法は、1 つのディレクトリ内では 1 つのプログラムまたはライブラリしか作成しないことです。

テンプレート定義の検索

定義分離テンプレート編成 (テンプレートを使用するファイルの中にテンプレートの宣言だけがあって定義はないという編成) を使用している場合には、現在のコンパイル単位にテンプレート定義が存在しないので、コンパイラが定義を検索しなければなりません。この節では、そうした検索について説明します。

定義の検索はかなり複雑で、エラーを発生しやすい傾向があります。したがって、定義検索の必要がない定義取り込み型テンプレートファイル編成を使用するようにしてください。詳細については『C++ プログラミングガイド』を参照してください。


注 - -template=no%extdef オプションを使用する場合、コンパイラは別のソースファイルを検索しません。

ソースファイルの位置規約

オプションファイルで提供されるような特定の指令がない場合には、コンパイラは cfront 形式の方法でテンプレート定義ファイルを検出します。この方法では、テンプレート定義ファイルがテンプレート宣言ファイルと同じベース名を持ち、しかも現在の include パスにも存在している必要があります。たとえば、テンプレート関数 foo() が foo.h 内にある場合には、それと一致するテンプレート定義ファイルの名前を foo.cc か、または他の何らかの認識可能なソースファイル拡張子 (.C、.c、.cc、.cpp、.cxx) にしなければなりません。テンプレート定義ファイルは、通常使用する include ディレクトリの 1 つか、またはそれと一致するヘッダーファイルと同じディレクトリの中に置かなければなりません。

定義検索パス

-I で設定する通常の検索パスの代わりに、オプションの -ptidirectory (ディレクトリ) でテンプレート定義ファイルの検索ディレクトリを指定することができます。複数の -ptidirectoryフラグは、複数の検索ディレクトリ、つまり 1 つの検索パスを定義します。-ptidirectory を使用している場合には、コンパイラはこのパス上のテンプレート定義ファイルを探し、-I フラグを無視します。しかし、-ptidirectory フラグはソースファイルの検索規則を複雑にするので、-ptidirectory フラグより -I フラグを使用してください。

テンプレートインスタンスの自動一貫性

テンプレートレポジトリマネージャは、レポジトリ中のインスタンスの状態をソースファイルと確実に一致させて最新の状態にします。

たとえば、ソースファイルが -g オプション (デバッグ付き) でコンパイルされる場合には、データベースの中の必要なファイルも -g でコンパイルされます。

さらに、テンプレートレポジトリはコンパイル時の変更を追跡します。たとえば、
-DDEBUG フラグを指定して名前 DEBUG を定義すると、データベースがこれを追跡します。その次のコンパイルでこのフラグを省くと、コンパイラはこの依存性が設定されているテンプレートを再度インスタンス化します。

コンパイル時のインスタンス化

インスタンス化とは、C++ コンパイラがテンプレートから使用可能な関数やオブジェクトを作成するプロセスをいいます。Sun Workshop 6 C++ コンパイラ ではコンパイル時にインスタンス化を行います。つまり、テンプレートへの参照がコンパイルされているときに、インスタンス化が行われます。

コンパイル時のインスタンス化の長所を次に示します。

ソースファイルが異なるディレクトリに存在する場合、またはテンプレートシンボルを指定してライブラリを使用した場合には、テンプレートが複数回にわたってインスタンス化されることがあります。

テンプレートオプションファイル

テンプレートオプションファイルとは、テンプレート定義を特定したり、インスタンスを再コンパイルする際に必要なオプションを含む、ユーザーが用意するファイルです (省略も可)。このファイルを使ってテンプレートの特殊化と明示的なインスタンス化を制御することもできます。しかし、現在特殊化の宣言と明示的なインスタンス化に必要な構文はソースコード中で使用できるため、テンプレートオプションをこの用途に使用すべきではありません。


注 - テンプレートオプションファイルは、C++ コンパイラの将来のリリースではサポートされなくなる可能性があります。

オプションファイルの名前は CC_tmpl_opt で、SunWS_config ディレクトリ内にあります。このディレクトリ名は SUNWS_CONFIG_NAME 環境変数で変更できます。

オプションファイルは ASCII テキストファイルで、多くのエントリを含んでいます。エントリはキーワードから始まり、テキストが続き、セミコロン (;) で終わります。エントリは複数行に渡ってもかまいませんが、キーワードは必ず 1 行中に納めるようにしてください。分割してはなりません。

コメント

コメントは # 文字で始まり、その行の終わりまで続きます。コメント内のテキストは無視されます。

# コメント中のテキストは行末まで無視される。

インクルード

オプションファイルをインクルードすれば、複数のテンプレートデータベース間でオプションファイルを共有できます。この機能は特に、テンプレートを含むライブラリを構築するときに便利です。処理中、指定されたオプションファイルは原文どおりに現在のオプションファイルにインクルードされます。オプションファイル内では、複数の include 文をどこにでも指定できます。オプションファイルは入れ子にすることもできます。options-file はオプションファイル名を示します。

include "options-file";

ソースファイルの拡張子

コンパイラがデフォルトの Cfront 形式のソースファイル検索機構を使用している場合、extensions エントリを使用すれば、コンパイラが検索するソースファイルの拡張子を指定できます。次に、このエントリの構文を示します。

extensions "ext-list";

ext-list には有効なソースファイルの拡張子を、空白文字で区切って指定します。

extensions ".CC .c .cc .cpp";

このエントリがオプションファイルに存在しない場合、コンパイラが検索する拡張子は、.cc.c.cpp.C および .cxx です。

定義ソースの位置

定義ソースファイルの位置は、オプションファイル中の definition エントリで明示的に指定できます。definition エントリは、テンプレートの宣言と定義のファイル名が標準の Cfront 形式の規約に準拠していない場合に使用してください。次に、このエントリの構文を示します。

definition name in "file-1",[ "file-2" ..., "file-n"] [nocheck "options"];

name フィールドには、このエントリでの指定を適用するテンプレートを指定します。1 つの name に使用できる definition エントリは 1 つだけです。name に指定する名前は単純な名前である必要があります。つまり、修飾名は使用できません。また、丸かっこ、戻り型、およびパラメータリストも使用できません。戻り型やパラメータに関わらず、名前そのものだけが重要です。結果として、definition エントリは複数の (おそらくは、多重定義された) テンプレートに適用される可能性があります 。

file-n」リストフィールドには、テンプレート定義が含まれているファイルを指定します。ファイルの検索には、定義検索パスが使用されます。ファイル名は引用符 (") で囲む必要があります。複数のファイルを指定する理由は、指定した単純なテンプレート名がファイルごとに定義されている複数の異なるテンプレート名を参照していたり、1 つのテンプレートの定義がファイルごとに異なっている可能性があるためです。たとえば、func が 3 つのファイルで定義されている場合、これら 3 つのファイルを definition エントリのリストに指定する必要があります。

nocheck フィールドについては、この節の最後で説明します。

次の例では、コンパイラは foo.cc にあるテンプレート関数 foo を見つけ、この関数をインスタンス化します。ただし foo.cc 中の関数 foo はデフォルトの検索でも検出されるため、この definition エントリは冗長です。

コード例 4-1   冗長な definition エントリ
foo.cc template <class T> T foo( T t ) { }
CC_tmpl_opt definition foo in "foo.cc";


次の例では、静的なデータメンバーの定義と単純名の使用を示します。

コード例 4-2   静的なデータメンバーの定義と単純名の使用
foo.h template <class T> class foo { static T* fooref; };
foo_statics.cc #include "foo.h"
template <class T> T* foo<T>::fooref = 0
CC_tmpl_opt definition fooref in "foo_statics.cc";


fooref の定義で使用されている名前は単純名であり、修飾名 (foo::fooref など) ではありません。この definition エントリを定義する理由は、ファイル名が認識可能な拡張子ではなく (foo.cc など)、デフォルトの Cfront 形式の検索規則ではファイルを見つけることができないためです。

次の例では、テンプレートメンバー関数の定義を示します。例に示すとおり、メンバー関数は静的メンバー初期設定子とまったく同じように処理されます。

コード例 4-3   テンプレートメンバー関数の定義
foo.h template <class T> class foo { T* foofunc(T); };
foo_funcs.cc #include "foo.h"
template <class T> T* foo<T>::foofunc(T t) {}
CC_tmpl_opt definition foofunc in "foo_funcs.cc";


次の例では、2 つの異なるソースファイルにあるテンプレート関数の定義を示します。

コード例 4-4   異なるソースファイルにあるテンプレート関数の定義
foo.h
template <class T> class foo { 
    T* func( T t );
    T* func( T t, T x );
}; 
foo1.cc #include "foo.h"
template <class T> T* foo<T>::func( T t ) { }
foo2.cc #include "foo.h"
template <class T> T* foo<T>::func( T t, T x ) { }
CC_tmpl_opt definition func in "foo1.cc", "foo2.cc";


この例では、コンパイラは多重定義されている関数 func() の定義を両方とも見つける必要があります。そこで、definition エントリで、どこに適切な関数定義があるのかをコンパイラに指示します。

コンパイルフラグが変更されても、再コンパイルが不必要な場合もあります。オプションファイルの definition エントリに nocheck フィールドを指定すると、不必要な再コンパイルを回避できます。nocheck フィールドでオプションを指定すると、コンパイラとテンプレートデータベースマネージャは、そのオプションを依存関係の検査対象から除外します。特定のコマンド行フラグを追加または削除したために、コンパイラがテンプレート関数を再インスタンス化する必要がない場合は、nocheck フラグを使用してください。次に、このエントリの構文を示します。

definition name in "file-1"[, "file-2" ..., "file-n"] [nocheck "options"];

オプション (options) は引用符 (") で囲む必要があります。

次の例では、コンパイラは foo.cc にあるテンプレート関数 foo を見つけ、この関数をインスタンス化します。後で再インスタンス化のための検査が必要な場合、コンパイラは -g オプションを無視します。

コード例 4-5   nocheck オプション
foo.cc template <class T> T foo( T t ) {}
CC_tmpl_opt definition foo in "foo.cc" nocheck "-g";


テンプレートの特殊化エントリ

最近まで、C++ 言語はテンプレートを特殊化するための機構を持っておらず、個々のコンパイラが独自の機能を提供していました。この節では、以前のバージョンの C++ コンパイラの機構を使用したテンプレートの特殊化を説明します。この機構は、互換モード (-compat[=4]) でのみサポートされています。

special エントリは、指定された関数が特殊化であり、この関数に遭遇してもインスタンス化してはならないことをコンパイラに指示します。コンパイル時インスタンス化方法を使用する場合は、オプションファイルの中で special エントリを使用して、特殊化を事前に登録してください。次に、このエントリの構文を示します。

special declaration;

宣言 (declaration) には、戻り型がない正しい C++ 形式の宣言を指定します。次に例を示します。

コード例 4-6   special エントリ
foo.h template <class T> T foo( T t ) { };
main.cc #include "foo.h"
CC_tmpl_opt special foo(int);


上記の special エントリを含むオプションファイルは、テンプレート関数 foo()intd 型にインスタンス化してはならないこと、および、特殊化された関数 foo() がユーザーから提供されることをコンパイラに指示します。このエントリをオプションファイルに指定しない場合、関数は不必要に再インスタンス化され、その結果、エラーが発生します。

コード例 4-7   special エントリを使用する必要がある場合
foo.h template <classT> T foo( T t ) { return t + t; }
file.cc #include "foo.h"
int func( ) { return foo( 10 ); }
main.cc #include "foo.h"
int foo( int i ) { return i * i; } //
特殊化
int main( ) { int x = foo( 10 ); int y = func();
return 0; }


上記の例では、main.cc をコンパイルするとき、コンパイラはその定義をあらかじめ確認しているため、特殊化された foo を正しく使用します。しかし、file.cc をコンパイルするとき、コンパイラは main.ccfoo が存在することを知らないため、 foo に対して独自のインスタンス化を行います。この結果、ほとんどの場合は、このリンク中にシンボルが複数回定義されるだけですが、場合によっては (特にライブラリの場合)、間違った関数が使用され、実行時エラーが発生することがあります。特殊化された関数を使用する場合は、その特殊化を登録しておくことをお勧めします。

special エントリは多重定義できます。次に例を示します。

コード例 4-8   special エントリの多重定義
foo.h template <classT> T foo( T t ) {}
main.cc #include "foo.h"
int foo( int i ) {}
char* foo( char* p ) {}
CC_tmpl_opt special foo(int);
special foo(char*);


テンプレートクラスを特殊化するには、special エントリにテンプレート引数を指定します。

コード例 4-9   テンプレートクラスの特殊化
foo.h template <class T> class Foo { ... various members ... };
main.cc #include "foo.h"
int main( ) { Foo<int> bar; return 0; }
CC_tmpl_opt special class Foo<int>;


テンプレートクラスメンバーが静的なメンバーの場合、special エントリにキーワード static を指定する必要があります。

コード例 4-10   静的テンプレートクラスメンバーの特殊化
foo.h template <class T> class Foo { public: static T func(T); };
main.cc #include "foo.h"
int main( ) { Foo<int> bar; return 0; }
CC_tmpl_opt special static Foo<int>::func(int);



サン・マイクロシステムズ株式会社
Copyright information. All rights reserved.
ホーム   |   目次   |   前ページへ   |   次ページへ   |   索引