web-dev-qa-db-ja.com

ヘッダーファイルと.cppファイルがあるのはなぜですか。

C++にヘッダファイルと.cppファイルがあるのはなぜですか?

448
Clueless

まあ、主な理由はインターフェースを実装から切り離すことです。ヘッダはクラス(あるいは実装されているもの)が何をするかを宣言し、cppファイルはそれらの機能をどのように実行するかを定義します。

これは依存性を減らしますので、ヘッダを使うコードは必ずしも実装の詳細やそれに必要な他のクラス/ヘッダの詳細をすべて知る必要はありません。これにより、コンパイル時間が短縮され、実装内の何かが変更されたときに必要な再コンパイルの量も削減されます。

それは完璧ではありません、そしてあなたはインターフェースと実装を適切に分離するために Pimpl Idiom のようなテクニックに頼るでしょうが、それは良いスタートです。

190

C++コンパイル

C++でのコンパイルは、主に2つのフェーズで行われます。

  1. 1つ目は、「ソース」テキストファイルをバイナリの「オブジェクト」ファイルにコンパイルすることです。CPPファイルはコンパイル済みファイルで、生の宣言またはライブラリを介して渡されない限り、他のCPPファイル(またはライブラリ)に関する知識なしでコンパイルされます。ヘッダーを含めるCPPファイルは通常、.OBJファイルまたは.O "object"ファイルにコンパイルされます。

  2. 2つ目は、すべての「オブジェクト」ファイルのリンク、つまり最終的なバイナリファイル(ライブラリまたは実行可能ファイル)の作成です。

HPPはこのプロセス全体のどこに当てはまりますか?

貧弱な寂しいCPPファイル...

各CPPファイルのコンパイルは、他のすべてのCPPファイルから独立しています。つまり、A.CPPがB.CPPで定義されているシンボルを必要とする場合は、次のようになります。

// A.CPP
void doSomething()
{
   doSomethingElse(); // Defined in B.CPP
}

// B.CPP
void doSomethingElse()
{
   // Etc.
}

A.CPPに "doSomethingElse"が存在することを知る方法がないため、コンパイルできません... A.CPPに次のような宣言がない限り、

// A.CPP
void doSomethingElse() ; // From B.CPP

void doSomething()
{
   doSomethingElse() ; // Defined in B.CPP
}

次に、同じ記号を使用しているC.CPPがある場合は、宣言をコピー/貼り付けします。

コピー/貼り付けアラート!

はい、問題があります。コピー/貼り付けは危険で、維持が困難です。つまり、コピーや貼り付けをしないでシンボルを宣言する方法があるとしたら、それはクールだと思います。通常は.h、.hxx、.h ++、またはC++ファイルの場合は.hppの接尾辞が付いたテキストファイルをインクルードします。

// B.HPP (here, we decided to declare every symbol defined in B.CPP)
void doSomethingElse() ;

// A.CPP
#include "B.HPP"

void doSomething()
{
   doSomethingElse() ; // Defined in B.CPP
}

// B.CPP
#include "B.HPP"

void doSomethingElse()
{
   // Etc.
}

// C.CPP
#include "B.HPP"

void doSomethingAgain()
{
   doSomethingElse() ; // Defined in B.CPP
}

includeはどのように機能しますか?

ファイルを含めると、本質的には、解析してCPPファイルの内容をコピー&ペーストします。

たとえば、次のコードでは、A.HPPヘッダーを使用しています。

// A.HPP
void someFunction();
void someOtherFunction();

...ソースB.CPP:

// B.CPP
#include "A.HPP"

void doSomething()
{
   // Etc.
}

...包含後になります:

// B.CPP
void someFunction();
void someOtherFunction();

void doSomething()
{
   // Etc.
}

ちょっとしたこと - なぜB.HPPをB.CPPに含めるのですか?

現在の場合、これは必要ではなく、B.HPPはdoSomethingElse関数宣言を持ち、B.CPPはdoSomethingElse関数定義を持ちます(つまり、それ自体が宣言です)。しかし、より一般的なケースでは、B.HPPが宣言(およびインラインコード)に使用される場合、対応する定義(たとえば、列挙型、プレーン構造体など)がない可能性があるため、B.CPPの場合はインクルードが必要になります。 B.HPPからの宣言を使用します。結局のところ、デフォルトでヘッダを含めることはソースにとって "良い味"です。

結論

C++コンパイラは単独でシンボル宣言を検索できないため、ヘッダーファイルが必要になります。したがって、それらの宣言を含めることでそれを手助けする必要があります。

最後の言葉:あなたがあなたのHPPファイルの内容の周りにヘッダガードを置くべきであるということは、複数のインクルードが何も壊さないことを確実にするためです。

#ifndef B_HPP_
#define B_HPP_

// The declarations in the B.hpp file

#endif // B_HPP_
566
paercebal

この概念の由来であるCは30年前の、そして当時はCが、複数のファイルからコードをリンクする唯一の実行可能な方法だったためです。

今日では、C++でのコンパイル時間を完全に破壊し、無用な依存関係を無数に発生させる(ハッカーファイル内のクラス定義が実装に関する情報を公開しすぎるため)ひどいハックです。

88
jalf

C++では、最終的な実行可能コードにはシンボル情報が含まれていないため、多かれ少なかれ純粋なマシンコードです。

したがって、コード自体とは別の、コードのインターフェースを記述する方法が必要です。この説明はヘッダーファイルにあります。

52
unwind

C++がCから継承したためです。残念ながら。

13
andref

ライブラリフォーマットを設計した人々は、Cプリプロセッサマクロや関数宣言のようなめったに使われない情報のためにスペースを "浪費"したくないからです。

「この関数は後でリンカがその仕事をしているときに利用可能です」とコンパイラに伝えるためにその情報が必要なので、この共有情報を格納できる2つ目のファイルを考え出す必要がありました。

C/C++以降のほとんどの言語は、この情報を出力に格納するか(Javaバイトコードなど)、プリコンパイル形式をまったく使用せず、常にソース形式で配布され、その場でコンパイルされます(Python、Perl)。

12
Aaron Digulla

これはインタフェースを宣言するプリプロセッサの方法です。インターフェイス(メソッド宣言)をヘッダファイルに、実装をcppに入れます。あなたのライブラリを使用するアプリケーションは、それらが#includeを通してアクセスすることができるインターフェースを知る必要があるだけです。

4

多くの場合、コード全体を出荷しなくても、インターフェースの定義が必要になります。たとえば、共有ライブラリがある場合は、共有ライブラリで使用されるすべての関数とシンボルを定義したヘッダファイルを同梱します。ヘッダファイルがなければ、ソースを出荷する必要があります。

1つのプロジェクト内で、少なくとも2つの目的でヘッダーファイルが使用されます(私見)。

  • 明確さ、つまり、インターフェースを実装から分離することによって、コードを読みやすくなります。
  • 時間をコンパイルします。完全な実装ではなく可能な限りインタフェースのみを使用することで、コンパイラは実際のコードを解析する必要はなく(単に実行する必要があるだけでなく)インタフェースを参照するだけでよいため、コンパイル時間を短縮できます一回)。
4
Dan