C++ プログラムのファイル構成には、C プログラムの通常の場合よりも注意が必要です。この章では、ヘッダーファイル、インライン関数定義、およびテンプレート定義の設定方法について説明します。
効率的なヘッダーファイルを作成するのは困難な場合があります。通常ヘッダーファイルは、C と C++ 両方のさまざまなバージョンに適応させなければなりません。テンプレートを用意する場合は、2 度以上のインクルードが可能 (べき等) で、他のファイルを必要としないものにしてください。
ヘッダーファイルは、C と C++ プログラムの両方にインクルードできるようにしなければならない場合があります。しかし、従来型の C としても知られる「Kernighan and Ritchie C」(K&R C)、ANSI C (日本語版は 『注解 C++ リファレンスマニュアル』) の C++ (ARM C++)、および ISO C++ は、1 つのヘッダーファイル内の同じプログラム要素に対して異なる宣言や定義を規定している場合があります (言語およびバージョンによる違いの詳細は『C++ 移行ガイド』を参照)。これらの標準のすべてに受け入れられるようにヘッダーファイルを設定するには、プリプロセッサマクロの __STDC__ と __cplusplus が存在するかどうか、また、存在する場合はその値が何であるかに応じて、条件付きコンパイルを使用しなければならない場合があります。
マクロ __STDC__ は、K&R C では定義されていませんが、ANSI C と C++ では定義されています。このマクロは、K&R C コードを ANSI C または C++ のコードと分ける際に使用してください。このマクロは、プロトタイプ宣言された関数定義とプロトタイプ宣言されていない関数定義を分けるのに最も便利です。
#ifdef _ _STDC_ _ int function(char*,...); // C++ および ANSI C の宣言 #else int function(); // K&R C #endif
マクロ __cplusplus は C では定義されていませんが、C++ では定義されています。
C++ の初期のバージョンでは、マクロ __cplusplus ではなく c_plusplus が定義されていました。マクロ c_plusplus は、現在は定義されていません。
__cplusplus の定義は、C と C++ を分ける際に使用してください。このマクロは、次の例に示すように関数宣言に対する extern "C" インタフェースの指定を保護するのに最適です。extern "C" の指定の矛盾を防ぐため、extern "C" リンケージ指定のスコープ内には #include 指令を入れないでください。
#include“header.h” //... 他のインクルードファイル ... #if defined(_ _cplusplus extern“C”{ #endif int g1(); int g2(); int g3() #if defined(_ _cplusplus) } #endif
ARM C++ では、__cplusplus マクロの値は 1 です。ISO C++ では、このマクロの値は 199711L (この規格が制定された年月を long 定数として表現したもの) です。このマクロの値は、ARM C++ を ISO C++ と分ける際に使用してください。このマクロの値は、テンプレート構文内の変更を保護するのに最適です。
// テンプレート関数の特殊化 #if _ _cplusplus < 199711L int power(int,int); // ARM C++ #else template <> int power(int,int); // ISO C++ #endif
ヘッダーファイルはべき等でなければなりません。つまり、ヘッダーファイルを何度インクルードしても、その効果は 1 度だけインクルードする場合と同じでなければなりません。この特性は、テンプレートを作成する場合に特に重要です。ヘッダーファイルをべき等にするには、ヘッダーファイルの本体が 2 度以上出現することを防ぐプリプロセッサ条件を設定すると最も効果的です。
#ifndef HEADER_H #define HEADER_H /* ヘッダーファイルの内容 */ #endif
ヘッダーファイルには、完全なコンパイルに必要な定義がすべて含まれている必要があります。ヘッダーファイルは、必要な定義を含むヘッダーファイルをすべてその中にインクルードし、「自己完結」である、つまり他のファイルを必要としないように設定してください。
#include“another.h” /* another.h に依存する定義 */
通常、ヘッダーファイルは、べき等であるとともに自己完結でなければなりません。
#ifndef HEADER_H #define HEADER_H #include“another.h” /* another.h に依存する定義 */ #endif
C++ で書かれたプログラムでは通常 C プログラムよりも宣言の数が多く、そのためコンパイル時間が C プログラムよりも長くなります。宣言の数は、いくつかの手法を使用することにより減らせます。
手法の 1 つは、ヘッダーファイルをべき等にするように定義されたマクロを使用して、ヘッダーファイル自体を条件付きでインクルードするものです。ただしこの手法は、ファイル間の依存を増やします。
#ifndef HEADER_H #include“header.h” #endif
システムヘッダーファイルには、_Xxxx (X は大文字) という書式の識別子が含まれていることがよくあります。これらの識別子は予約されているため、保護のためのマクロでは、この書式の識別子は使用しないでください。
コンパイル時間を減らす別の方法として、定義を含むヘッダーファイルをインクルードせずに、不完全なクラスと構造体 (struct) 宣言またはクラス (class) 宣言を使用できます。この手法は、完全な定義が不要で、識別子が typedef でも template でもなく、class または struct である場合に使用できます (標準ライブラリは、クラスではなくテンプレートである typedef を多数含む)。たとえば、次のように記述する代わりに、
#include“class.h” a_class* a_ptr;
class a_class; a_class* a_ptr;
(a_class が実際に typedef である場合、この手法は無効です)
もう 1 つの方法として、Erich Gamma 著の『オブジェクト指向における再利用のためのデザインパターン』 (ソフトバンク) に述べられているように、インタフェースクラスとファクトリを使用することもできます。
インライン関数の定義を構成する方法は 2 つあります。その 1 つは定義をインライン展開するもので、もう 1 つは定義を取り込むものです。どちらの手法にも、利点と欠点があります。
定義のインライン展開による構成は、メンバー関数にしか使用できません。インライン展開するには、クラス定義内の関数宣言の後に関数の本体を直接入れます。
class Class { int method() { return 3; } };
この構成は、関数のプロトタイプの繰り返しを防ぎ、ソースファイルの容量を減らし、不整合が起きる可能性を減らします。しかし、この構成は、通常はインタフェースとして扱われる部分に、実装の詳細を挿入する場合があります。この場合は、関数がインライン展開でなくなったときに、大幅な編集が必要になります。
この構成は、関数の本体がごく少量である (つまり空の中括弧) であるか、あるいは関数が常にインラインである場合にのみ使用してください。
定義取り込み型の構成は、すべてのインライン関数に使用できます。関数の本体を、プロトタイプの繰り返し (必要な場合) とともに入れてください。関数定義は、ソースファイル内に直接入れることも、ソースファイルとともに取り込むこともできます。
class Class { int method(); }; inline int Class::method() { return 3; }
この構成は、インタフェースと実装が分離されます。このため関数がインラインで実装されなくなったときには、定義をヘッダーファイルからソースファイルに簡単に移動できます。欠点は、この構成ではクラスのプロトタイプが繰り返されることです。この繰り返しのため、ソースファイルの容量が増え、矛盾が発生しやすくなります。
テンプレート定義は、2 つの方法で構成できます。1 つは定義取り込み型で、もう 1 つは定義分離型です。定義取り込み型の構成の方が、テンプレートのコンパイルをより広範囲に渡って制御できます。
テンプレートを使用するファイルの中にテンプレートの宣言と定義が含まれていれば、そのファイルは定義取り込み型の構成です。次にその例を示します。
main.cc template <class Number> Number twice( Number original ); template <class Number> Number twice( Number original ) { return original + original; } int main( ) { return twice<int>( -3 ); }
テンプレートを使用するファイルがテンプレートの宣言と定義の両方を含むファイルをインクルードしている場合、そのテンプレートを使用するファイルも定義取り込み型の構成になります。次にその例を示します。
twice.h #ifndef TWICE_H #define TWICE_H template <class Number> Number twice( Number original ); template <class Number> Number twice( Number original ) { return original + original; } #endif main.cc #include“twice.h” int main( ) { return twice( -3 ); }
ここでは、テンプレートヘッダーをべき等にすることが重要です (「べき等なヘッダーファイル」を参照)。
テンプレート定義を構成するには、次の例に示すように、テンプレート定義ファイルに定義を入れることもできます。
twice.h template <class Number> Number twice( Number original ); twice.cc template <class Number> Number twice( Number original ) { return original + original; } main.cc #include“twice.h” int main( ) { return twice<int>( -3 ); }
テンプレート定義ファイルにインクルードするヘッダーファイルは必ずべき等でなければなりません。
実際にはヘッダーファイルのインクルードをまったく必要としないこともよくあります (「べき等なヘッダーファイル」を参照)。
テンプレート定義ファイルには一般的にソースファイルの拡張子 (.c、.C、.cc、.cpp、.cxx) が使用されますが、テンプレート定義ファイルは実際にはヘッダーファイルです。コンパイラは、必要に応じてそれらを自動的にインクルードします。テンプレート定義ファイルは、個別にコンパイルするべきものではありません。
テンプレート宣言とテンプレート定義を別々のファイルに置く場合は、その定義ファイルの構成、名前、置き場所に細心の注意を払う必要があります。定義の置き場所を、コンパイラに対して明示的に知らせることが必要になる場合もあります。テンプレート定義の検索に関する規則については、『C ++ ユーザーズガイド』を参照してください。