コンピュータプログラミングにおいて、前方宣言とは、プログラマがまだ完全な定義を与えていない識別子(型、変数、定数、関数などの実体を表す)の宣言のことである。
コンパイラは、識別子の特定のプロパティ(メモリ割り当てのためのサイズ、型チェックのためのデータ型、関数の型シグネチャなど)を知る必要がありますが、それが保持する具体的な値(変数や定数の場合)や定義(関数の場合)といったその他の詳細を知る必要はありません。これは、ワンパスコンパイラや分離コンパイルにおいて特に役立ちます。
前方宣言は、使用前に宣言が必要な言語で使用されます。このような言語では相互再帰に不可欠であり、1つの定義内で前方参照なしに関数(またはデータ構造)を定義することは不可能です。つまり、いずれかの関数(またはデータ構造)を先に宣言する必要があります。また、例えばメインのコードを先頭に配置し、その下に呼び出される関数を配置するなど、柔軟なコード構成を可能にするためにも役立ちます。
他の言語では前方宣言は不要であり、一般的にはマルチパスコンパイラと、コンパイルの一部をリンク時に延期する処理が必要となります。このような場合、識別子はコンパイルや解釈のためにソースコードで事前に定義する必要はなく、実行時に使用できるように定義(変数の初期化、関数の定義)しておく必要があります。つまり、識別子を既存のエンティティに即座に解決する必要はありません。
C言語における基本的な例は次のとおりです。
void printThisInteger ( int );CおよびC++では、上記の行は関数の前方宣言を表し、関数のプロトタイプです。この宣言を処理した後、コンパイラはプログラムコードがプログラムの残りの部分でそのエンティティを参照できるようにしますprintThisInteger。関数の定義はどこかに指定する必要があります (同じファイルでも別のファイルでも、リンカは 1 つまたは複数のオブジェクトファイル内の特定の関数への参照を、別のファイル内の一意である定義と正しく照合する責任があります)。
void printThisInteger ( int x ) { printf ( "%d \n " , x ); }変数は前方宣言のみを持ち、定義を持たない場合があります。コンパイル時に、これらの変数は言語固有の規則に従って初期化されます(未定義値、0、NULLポインタなど)。他のソース/オブジェクトファイルで定義されている変数は、キーワード : で前方宣言を指定する必要がありますextern。
int foo ; //foo はこのファイルのどこかで定義されている可能性がありますextern int bar ; //bar は別のファイルで定義されている必要がありますPascalやその他のWirthプログラミング言語では、すべてのエンティティは使用前に宣言する必要があるというのが一般的なルールであり、例えば相互再帰には前方宣言が必要です。C言語でも同じ一般的なルールが適用されますが、未宣言の関数と不完全な型は例外です。したがって、C言語では、相互再帰関数のペアを次のように実装することが可能です(ただし、賢明ではありません)。
int first ( int x ) { if ( x == 0 ) return 1 ; else return second ( x -1 ); // second への前方参照}int second ( int x ) { if ( x == 0 ) return 0 ; else return first ( x -1 ); // first への後方参照}Pascalでは、同じ実装を行うには、secondの使用に先立って の前方宣言が必要ですfirst。前方宣言がない場合、コンパイラは、識別子が宣言されずに使用されていることを示すエラーメッセージを生成します。second
C++やObjective-Cのようなオブジェクト指向言語では、クラスを前方宣言する必要がある場合があります。これは、クラス名が型であることを知る必要があるが、その構造を知る必要がない場合に行われます。
C++では、クラスと構造体は次のように前方宣言できます。
class MyClass ; struct MyStruct ;C++では、クラスへのポインタ型のみを使用すればよい場合、クラスを前方宣言することができます(すべてのオブジェクトポインタは同じサイズであり、コンパイラが気にするのはサイズであるため)。これは、クラス定義内で特に役立ちます。たとえば、クラスが別のクラスへのポインタ(または参照)であるメンバを含む場合などです。
前方宣言は、不要な結合を回避するために使用され、ヘッダーファイルのインクルード数を減らすことでコンパイル時間を短縮するのに役立ちます。これには3つの利点があります。
クラスの前方宣言だけでは、実際のクラス型を使用する必要がある場合(たとえば、そのクラス型を直接使用するメンバーがある場合(ポインタではない)、基底クラスとして使用する必要がある場合、またはメソッド内でクラスのメソッドを使用する必要がある場合)には不十分です。
Objective-Cでは、クラスとプロトコルは次のように前方宣言できます。
@class MyClass ; @protocol MyProtocol ; Objective-C では、クラスやプロトコルをオブジェクトポインタ型の一部としてのみ使用する必要がある場合、例えばMyClass *やid<MyProtocol>のように前方宣言することができます。これは、クラス定義内で特に便利です。例えば、あるクラスが別のクラスへのポインタであるメンバーを含んでいる場合、循環参照(つまり、そのクラスもこのクラスへのポインタであるメンバーを含んでいる可能性がある)を避けるために、クラスを前方宣言するだけです。
クラスやプロトコルを前方宣言するだけでは、そのクラスをサブクラス化したり、そのプロトコルを実装したりする必要がある場合には不十分です。
前方参照という用語は、前方宣言の同義語として使われることがあります。[ 1 ]しかし、多くの場合、宣言の前にエンティティを実際に使用することsecondを指すと解釈されます。つまり、上記のコードの最初の参照は前方参照です。 [ 2 ] [ 3 ]したがって、Pascal では前方宣言が必須であるため、前方参照は禁止されていると言えます。
C++における(有効な)前方参照の例:
class C { public : void mutator ( int x ) { myValue = x ; } int accessor () const { return myValue ; } private : int myValue ; };この例では、宣言される前に への参照が 2 つありますmyValue。C++ では一般的に前方参照は禁止されていますが、クラスメンバの特殊なケースでは許可されています。メンバ関数は、コンパイラがメンバ変数accessorの型を認識するまでコンパイルできないため、の宣言を見るまでの定義を記憶しておくのはコンパイラの責任です。myValueaccessormyValue
前方参照を許可すると、コンパイラの複雑さとメモリ要件が大幅に増加し、一般的にコンパイラを1回のパスで実装することができなくなります。