web-dev-qa-db-ja.com

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

で: 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;
}

main.cppをコンパイルするときにコンパイラが "add"が何であるかを知ることができるように、前方宣言を使いました。前述のように、使用したいすべての関数の宣言を別のファイルに書いておくと、面倒な作業になります。

前方宣言」についてさらに説明してください。 main()関数で使用した場合の問題は何ですか?

187
Simplicity

なぜC++では前方宣言が必要なのか

コンパイラは、スペルミスをしたり、間違った数の引数を関数に渡したりしていないことを確認します。それで、それはそれが使用される前にそれが最初に 'add'(または他の型、クラスまたは関数)の宣言を見ることを主張します。

これは、コンパイラがコードを検証するためのより良い仕事をすることを本当に可能にし、見栄えのよいオブジェクトファイルを生成することができるようにルーズエンドを片付けることを可能にします。前方宣言する必要がなければ、コンパイラはオブジェクトファイルを生成します。このオブジェクトファイルには、関数 'add'がどのようなものであるかについて考えられるすべての推測に関する情報を含める必要があります。 'add'関数がaddを生成するためにaddを使用しているものと結合している別のオブジェクトファイルに存在する可能性がある場合、リンカには実際に呼び出そうとしている 'add'を試すことができます。 DLLやexeファイル。リンカが間違った追加をする可能性があります。 int add(int a、float b)を使用したいが、誤ってそれを記述するのを忘れたが、リンカはすでに存在するint add(int a、int b)を見つけ、それが正しいものだと考え、代わりにそれを使用したとします。あなたのコードはコンパイルされますが、あなたが期待したことをしていないでしょう。

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

宣言と定義の違い

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

どのように前方宣言はビルド時間を大幅に減らすことができます

すでに関数の宣言が含まれているヘッダーを#includeすることで、関数の宣言を現在の.cppまたは.hファイルに入れることができます。ただし、特にプログラムの.cppではなく.hにヘッダーを#includeすると、コンパイル速度が低下する可能性があります。#hを含めるとすべてのヘッダーが#​​includeされることになるためです。 #includesも書いた。突然、1つか2つの関数しか使いたくない場合でも、コンパイラはコンパイルする必要があるページとコードのページを#includeしました。これを回避するには、前方宣言を使用し、ファイルの先頭に関数の宣言を自分で入力します。少数の関数しか使用していない場合は、これにより、常にヘッダーを#includeする場合と比較して、コンパイルが速くなります。大規模プロジェクトの場合、違いは1時間以上のコンパイル時間から数分になる可能性があります。

2つの定義が互いに使用している場合は、循環参照を分割してください

さらに、前方宣言はサイクルを中断するのに役立ちます。これは、2つの関数が互いに使用しようとしている場所です。これが起こると(そしてそれがすることは完全に有効なことです)、あなたは1つのヘッダファイルを#includeすることができますが、そのヘッダファイルは現在書いているヘッダファイルを#includeしようとします。 #これはあなたが書いているものを含みます。あなたはそれぞれのヘッダファイルがもう一方を#includeしようとしている、鶏と卵の状況で立ち往生しています。これを解決するには、ファイルの1つに必要な部分を前方宣言し、そのファイルから#includeを除外します。

例えば:

File Car.h

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

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

File Wheel.h

うーん...ここではWheelがCarへのポインタを持っているのでCarの宣言が必要ですが、Car.hはコンパイラエラーになるのでここに含めることはできません。 Car.hがインクルードされている場合、それはWheel.hをインクルードするCar.hをインクルードするWheel.hをインクルードしようとしますが、これは永遠に続くので、代わりにコンパイラはエラーを起こします。解決策は、代わりにCarを前方宣言することです。

class Car;     // forward declaration

class Wheel
{
    Car* car;
};

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

347
Scott Langham

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

//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ヘッダファイルを持つ)関数だけを持っているなら、最初にプロトタイプを提供することはスタイルの問題です。とにかく、ヘッダファイルが機能だけを持つヘッダを持つ通常のプログラムの中に存在していたならば、これは事実です。

25
Mahesh

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

int add( int x, int y )

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

そのため、「Forward Declaration」というのは、まさにその意味でのことです。使用前に何かを宣言しています。

一般的には、ヘッダーファイルに前方宣言を含め、次にiostreamが含まれるのと同じ方法でそのヘッダーファイルを含めます。

11
Nick

C++での "前方宣言"という用語は、ほとんどの場合クラス宣言に対してのみ使用されます。クラスの「前方宣言」が本当に単純なクラス宣言で、派手な名前を付けたものである理由については、(の終わり) この答え を参照してください。

言い換えれば、any宣言はそれが使用される何らかの識別子beforeを宣言する限り前方にあると見なすことができるので、 "forward"は単にバラストを用語に追加するだけです。

定義とは対照的に、宣言とは何ですか?また、 定義と宣言の違いは何ですか?

10
sbi

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

当時、コンパイラはaddactual実装(つまり、存在する場合はis偶数が1つでも構いません)、およびコンパイルする場合については心配していません。これは後で明らかになります。afterリンカが呼び出されたときにソースファイルをコンパイルします。

2
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
Nawaz

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

extern int add(int a、int b); 

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

#include 
#include "add.h" 
 
 int main()
 {
。
 
。
 
1
jack

問題の1つは、コンパイラが、関数によってどの種類の値が提供されるのかがわからないことです。この場合、関数はintを返すと仮定していますが、これは正しくない可能性があります。もう1つの問題は、間違った種類の値を渡している場合、コンパイラは、関数がどの種類の引数を想定しているのかを認識できず、警告できないことです。浮動小数点値を宣言されていない関数に渡す(コンパイラはdouble型に拡張する必要がある)など、渡すときに適用される特別な "昇格"規則があります。実行時に.

0
Dirk