532

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

4

9 に答える 9

655

C++ コンパイル

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

  1. 1 つ目は、「ソース」テキスト ファイルをバイナリの「オブジェクト」ファイルにコンパイルすることです。CPP ファイルはコンパイル済みファイルであり、他の CPP ファイル (またはライブラリ) について何も知らずにコンパイルされます。ヘッダーの組み込み。CPP ファイルは通常、.OBJ または .O「オブジェクト」ファイルにコンパイルされます。

  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.CPP に B.HPP を含めるのですか?

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

結論

C++ コンパイラはシンボル宣言だけを検索することができないため、ヘッダー ファイルが必要です。

最後に一言: HPP ファイルのコンテンツの周りにヘッダー ガードを配置して、複数のインクルードによって何も壊れないようにする必要がありますが、全体として、HPP ファイルが存在する主な理由は上記で説明したとおりだと思います。

#ifndef B_HPP_
#define B_HPP_

// The declarations in the B.hpp file

#endif // B_HPP_

またはさらに単純です(ただし、標準ではありません)

#pragma once

// The declarations in the B.hpp file
于 2008-12-02T13:47:57.603 に答える
218

主な理由は、インターフェイスを実装から分離するためです。ヘッダーは、クラス (または実装されているもの) が実行する "内容" を宣言し、cpp ファイルはそれらの機能を実行する "方法" を定義します。

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

これは完璧ではありません。通常は、Pimpl Idiomのような手法を使用してインターフェイスと実装を適切に分離しますが、これは良い出発点です。

于 2008-12-02T13:23:38.683 に答える
102

この概念が生まれた C は 30 年前のものであり、当時は複数のファイルのコードをリンクする唯一の実行可能な方法でした。

今日、これは C++ でのコンパイル時間を完全に破壊し、無数の不要な依存関係を引き起こす (ヘッダー ファイル内のクラス定義が実装に関する情報を公開しすぎるため) などのひどいハックです。

于 2008-12-02T18:15:45.317 に答える
58

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

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

于 2008-12-02T13:19:55.820 に答える
21

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

于 2010-07-28T18:54:20.140 に答える
16

ライブラリ形式を設計した人々は、C プリプロセッサ マクロや関数宣言など、めったに使用されない情報のためにスペースを「無駄」にしたくありませんでした。

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

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

于 2008-12-02T13:23:49.650 に答える
6

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

于 2008-12-02T13:19:35.570 に答える
5

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

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

  • 明快さ、つまり、インターフェイスを実装から分離することで、コードが読みやすくなります
  • コンパイル時間。完全な実装ではなく、可能な限りインターフェースのみを使用することにより、コンパイラーは実際のコードを解析する必要がなく、インターフェースへの参照を作成するだけで済むため、コンパイル時間を短縮できます (これは、理想的には、実行するだけで済みます)。 1回)。
于 2008-12-02T13:25:03.110 に答える
-5

MadKeithV の回答に応えて、

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

もう 1 つの理由は、ヘッダーが各クラスに一意の ID を与えることです。

したがって、次のようなものがある場合

class A {..};
class B : public A {...};

class C {
    include A.cpp;
    include B.cpp;
    .....
};

A は B の一部であるため、プロジェクトをビルドしようとするとエラーが発生します。ヘッダーを使用すると、この種の頭痛を回避できます...

于 2012-02-29T23:08:30.200 に答える