C ++の前方宣言とは何ですか?


215

で:http : //www.learncpp.com/cpp-tutorial/19-header-files/

以下が言及されています:

add.cpp:

int add(int x, int y)
{
    return x + y;
}

main.cpp:

#include <iostream>

int add(int x, int y); // forward declaration using function prototype

int main()
{
    using namespace std;
    cout << "The sum of 3 and 4 is " << add(3, 4) << endl;
    return 0;
}

コンパイラがaddコンパイル時に" "が何であるかがわかるように、前方宣言を使用しましたmain.cpp。前述のように、別のファイルにある、使用したいすべての関数の前方宣言を書くのは、手間がかかります。

前向き宣言」について詳しく説明していただけますか?main()関数で使用するとどうなりますか?


1
「前方宣言」は実際には単なる宣言です。この回答を(の終わり)を参照してください:stackoverflow.com/questions/1410563/...
SBI

回答:


381

C ++で前方宣言が必要な理由

コンパイラーは、スペルミスや間違った数の引数を関数に渡していないことを確認したいと考えています。そのため、使用される前に、まず 'add'(またはその他の型、クラス、関数)の宣言を確認するように要求しています。

これにより、コンパイラーはコードの妥当性検査をより適切に実行できるようになり、ルーズエンドを整理して、見栄えの良いオブジェクトファイルを生成できるようになります。宣言を転送する必要がなかった場合、コンパイラーは、関数「add」が何であるかについて考えられるすべての推測に関する情報を含む必要があるオブジェクトファイルを生成します。そして、リンカは、「add」関数がaddを使用して生成するオブジェクトと結合している別のオブジェクトファイルに存在する場合に、実際に呼び出すつもりの「add」を試すための非常に巧妙なロジックを含む必要があります。 dllまたはexe。リンカーが誤った追加を取得する可能性があります。int add(int a、float b)を使用したいが、誤って書き込みを忘れたが、リンカが既存のint add(int a、int b)そしてそれは正しいものであると考え、代わりにそれを使用しました。コードはコンパイルされますが、期待どおりに動作しません。

そのため、物事を明確に保ち、​​推測などを避けるために、コンパイラは、使用する前にすべてを宣言するように要求します。

宣言と定義の違い

余談ですが、宣言と定義の違いを知ることは重要です。宣言は、何かがどのように見えるかを示すのに十分なコードを提供するだけなので、関数の場合、これは戻り値の型、呼び出し規約、メソッド名、引数、およびそれらの型です。ただし、メソッドのコードは必要ありません。定義には、宣言が必要であり、関数のコードも必要です。

前方宣言でビルド時間を大幅に削減する方法

関数の宣言を現在の.cppまたは.hファイルに取り込むには、関数の宣言が既に含まれているヘッダーを#includします。ただし、これによりコンパイルが遅くなる可能性があります。特に、プログラムの.cppではなく.hにヘッダーを#includeする場合、作成している.hを#includeするすべてのものがすべてのヘッダーを#includeすることになるためです。あなたも#includesを書きました。突然、コンパイラには#includeされたページとコードのページが含まれ、1つまたは2つの関数のみを使用したい場合でも、コンパイルする必要があります。これを回避するには、前方宣言を使用して、ファイルの先頭に関数の宣言を入力します。いくつかの関数のみを使用している場合、これにより、常にヘッダーを#includeするよりもコンパイルが速くなります。本当に大きなプロジェクトの場合、

2つの定義の両方が互いに使用する循環参照を解除する

さらに、前方宣言はサイクルを断ち切るのに役立ちます。これは、2つの関数の両方が互いに使用しようとする場所です。これが発生した場合(そしてそれを行うことは完全に有効なことです)、1つのヘッダーファイルを#includeすることができますが、そのヘッダーファイルは、現在作成しているヘッダーファイルを#includeしようとします。 、あなたが書いているものを#includeします。あなたは鶏と卵の状況に行き詰まり、各ヘッダーファイルはもう#includeを再組み込みしようとしています。これを解決するには、ファイルの1つで必要な部分を前方宣言し、そのファイルに#includeを残します。

例えば:

ファイルCar.h

#include "Wheel.h"  // Include Wheel's definition so it can be used in Car.
#include <vector>

class Car
{
    std::vector<Wheel> wheels;
};

Wheel.hファイル

うーん... WheelにはCarへのポインターがあるため、ここではCarの宣言が必要ですが、Car.hを含めることはできません。コンパイラエラーが発生するためです。Car.hがインクルードされた場合、それはWheel.hをインクルードするWheel.hをインクルードしようとし、これはWheel.hをインクルードし、これは永遠に続くため、コンパイラはエラーを発生させます。解決策は、代わりにCarをフォワード宣言することです:

class Car;     // forward declaration

class Wheel
{
    Car* car;
};

Wheelクラスにcarのメソッドを呼び出す必要があるメソッドがある場合、それらのメソッドはWheel.cppで定義でき、Wheel.cppはサイクルを引き起こすことなくCar.hを含めることができるようになりました。


4
関数が2つ以上のクラスに対応している場合も、前方宣言が必要です
Barun

1
ねえスコット、ビルド時間について:常に宣言し、.cppファイルに必要に応じてヘッダーを含めるのが一般的/ベストプラクティスだと思いますか?あなたの答えを読むと、それはそうであるように見えるでしょうが、何か注意点はあるのでしょうか?
Zepee 2015

5
@Zepeeそれはバランスです。クイックビルドの場合、これは良い方法だと思います。試すことをお勧めします。ただし、タイプ名などがまだ変更されている場合は、保守や更新が必要になる可能性のある追加のコード行が必要になります(ただし、ツールは自動的に名前を変更できるようになります)。したがって、トレードオフがあります。誰も気にしないコードベースを見てきました。同じ順方向定義を繰り返している場合は、いつでも別のヘッダーファイルに入れて、次のようなものを含めることができます。stackoverflow.com
Scott Langham

ヘッダーファイルが相互に参照する場合は、前方宣言が必要です。つまり、stackoverflow.com / questions / 396084 /…
Nicholas Hamilton

1
これにより、チームの他の開発者がコードベースの非常に悪い市民になることを確認できます。前方宣言でコメントが必要ない場合は、このように、将来// From Car.hの定義を見つけようとする毛深い状況を作成できます。
ダグルームズ2017年

25

コンパイラーは、現在の翻訳単位で使用されている各シンボルが以前に宣言されているか、現在の単位で宣言されていないかを検索します。定義は後で提供されますが、ソースファイルの先頭ですべてのメソッドシグネチャを提供することは、スタイルの問題です。このクラスの重要な用途は、クラスへのポインターを別のクラスのメンバー変数として使用する場合です。

//foo.h
class bar;    // This is useful
class foo
{
    bar* obj; // Pointer or even a reference.
};

// foo.cpp
#include "bar.h"
#include "foo.h"

そのため、可能な場合はクラスで前方宣言を使用してください。プログラムに(hoヘッダーファイル付きの)関数しかない場合は、最初にプロトタイプを提供するのはスタイルの問題です。これは、ヘッダーファイルが通常のプログラムに存在し、関数のみを含むヘッダーがある場合に発生します。


12

C ++はトップダウンで解析されるため、コンパイラーはそれらが使用される前にそれらについて知る必要があります。だから、あなたが参照するとき:

int add( int x, int y )

メイン関数では、コンパイラはそれが存在することを知る必要があります。これを証明するには、メイン関数の下に移動すると、コンパイラエラーが発生します。

したがって、「前方宣言」は、それが単に缶に書かれていることです。使用前に何かを宣言しています。

一般的には、フォワード宣言をヘッダーファイルに含め、そのヘッダーファイルをiostreamと同じ方法で含めます。


12

C ++の「前方宣言」という用語は、主にクラス宣言でのみ使用されます。クラスの「順方向宣言」が実際には単なるファンシーな名前の単純なクラス宣言である理由については、この回答(の終わり)を参照してください。

言い換えると、「転送」は、用語にバラストを追加するだけです。これは、使用に何らかの識別子を宣言する限り、どの宣言も転送であると見なすことができるためです。

定義ではなく宣言とは何かについては、定義と宣言の違い何ですか?を参照してください


2

コンパイラーは、add(3, 4)それが何を意味するかを知る必要があります。forward宣言では、基本的に、add2つのintを受け取り、intを返す関数であることをコンパイラーに伝えます。これは、4と5を正しい表現でスタックに配置する必要があり、addによって返されるものがどのタイプかを知る必要があるため、コンパイラにとって重要な情報です。

その時に、コンパイラが心配されていない実際の実装add、すなわち、それがどこにあるか(または存在している場合さえ1)と、それはコンパイルする場合。これは、後に見えてくるの後にリンカーが呼び出されたときに、ソースファイルをコンパイルします。


1
int add(int x, int y); // forward declaration using function prototype

「フォワード宣言」についてもう少し詳しく説明してもらえますか?main()関数で使用する場合の問題は何ですか?

と同じ#include"add.h"です。ご存知の場合、プリプロセッサは#include#includeディレクティブで記述した.cppファイルで、で言及したファイルを展開します。つまり、を書いた場合#include"add.h"と同じ結果が得られます。つまり、「前方宣言」を行った場合と同じです。

私はそれadd.hがこの行を持っていると仮定しています:

int add(int x, int y); 

1

簡単な補足事項:通常、これらの前方参照は、関数/変数などが実装されている.c(pp)ファイルに属するヘッダーファイルに入れます。あなたの例では次のようになります:add.h:

extern int add(int a、int b);

キーワードexternは、関数が実際に外部ファイルで宣言されていることを示しています(ライブラリなどの場合もあります)。main.cは次のようになります。

#include 
#include "add.h"

int main()
{
。
。
。


しかし、ヘッダーファイルに宣言を配置するだけではありませんか?これが、関数が「add.cpp」で定義されているため、前方宣言を使用する理由だと思いますか?ありがとう。
シンプルさ2011年

0

1つの問題は、コンパイラがどのような値を関数が提供しているかがわからないことです。これは、関数がintこの場合にを返すことを前提としていますが、これは間違っている場合と同じくらい正しい場合もあります。もう1つの問題は、間違った種類の値を渡した場合に、コンパイラがどのような種類の引数を期待しているのかを知らず、警告することができないことです。浮動小数点値を宣言されていない関数に渡すときに適用される特別な「昇格」規則があります(コンパイラーはそれらをdouble型に拡張する必要があります)。これは、関数が実際に期待するものではないため、バグを見つけるのが難しくなります。実行時。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.