ヘッダーファイルと.cppファイルがあるのはなぜですか?[閉まっている]


484

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



それは一般的なOOPパラダイムであり、.hはクラス宣言であり、cppは定義です。1つはそれがどのように実装されているかを知る必要はなく、インターフェイスを知っているだけです。
マニッシュカカティ2017

これは、インターフェイスを実装から分離するC ++の最良の部分です。すべてのコードを1つのファイルに保存するよりも、常に良い方法です。インターフェースを分離しています。ヘッダーファイルの一部であるインライン関数のように、常に一定量のコードが存在します。ヘッダーファイルが表示されたときに見栄えがよく、宣言された関数とクラス変数のリストが表示されます。
Miank

ヘッダーファイルがコンパイルに不可欠な場合があります-組織の設定や事前にコンパイルされたライブラリを配布する方法だけではありません。game.cがphysics.cとmath.cの両方に依存する構造があるとします。physics.cもmath.cに依存しています。.cファイルをインクルードし、.hファイルを永久に忘れてしまった場合、math.cからの宣言が重複し、コンパイルの希望がなくなります。これが、ヘッダーファイルが重要である理由を私に最も理解します。それが他の誰かを助けることを願っています。
Samy Bencherif

拡張機能では英数字のみが許可されていることに関係していると思います。それが本当かどうかはわかりませんが、推測するだけです
user12211554 '13年

回答:


202

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

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

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


178
本当ではない。ヘッダーにはまだ実装の主要部分が含まれています。プライベートインスタンス変数がクラスのインターフェイスの一部になったのはいつからですか。プライベートメンバーの機能?それで、彼らは誰にでも見えるヘッダーで何をしているのですか?そして、それはテンプレートによってさらにバラバラになります。
2008

13
それが完璧ではないと私が言った理由であり、Pimplイディオムはより多くの分離に必要です。テンプレートはまったく異なるワームの缶です。 "exports"キーワードがほとんどのコンパイラで完全にサポートされていたとしても、実際の分離ではなく構文上の砂糖になります。
Joris Timmermans、

4
他の言語はこれをどのように処理しますか?たとえば-Java?Javaにはヘッダーファイルの概念はありません。
Lazer

8
@Lazer:Javaは解析が簡単です。Javaコンパイラは、他のファイルのすべてのクラスを知らなくてもファイルを解析し、後で型をチェックできます。C ++では、型情報がないと多くの構造が曖昧であるため、C ++コンパイラは、ファイルを解析するために参照型に関する情報を必要とします。そのため、ヘッダーが必要です。
Niki、

15
@nikie:解析の「容易さ」はそれとどう関係するのですか?Javaに少なくともC ++と同じくらい複雑な文法があったとしても、Javaファイルをそのまま使用できます。どちらの場合でも、Cはどうですか?Cは解析が簡単ですが、ヘッダーとcファイルの両方を使用します。
Thomas Eding、2011

609

C ++コンパイル

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

  1. 1つは、「ソース」テキストファイルをバイナリの「オブジェクト」ファイルにコンパイルすることです。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.
}

小さなことの1つ-なぜ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

2
@nimcap::You still have to copy paste the signature from header file to cpp file, don't you?必要ありません。CPPがHPPを「含む」限り、プリコンパイラーはHPPファイルの内容をCPPファイルに自動的にコピーアンドペーストします。私はそれを明確にするために答えを更新しました。
paercebal 2012年

7
@ボブ:While compiling A.cpp, compiler knows the types of arguments and return value of doSomethingElse from the call itself。いいえ、ありません。ユーザーから提供された型しか認識しないため、半分の時間で戻り値を読み取る必要さえありません。次に、暗黙的な変換が行われます。そして、あなたがコードを持っているとき:foo(bar)、あなたはfoo関数であることを確信することさえできません。そのため、コンパイラーは、ソースが正しくコンパイルされるかどうかを判断するためにヘッダー内の情報にアクセスする必要があります...次に、コードがコンパイルされると、リンカーは関数呼び出しをリンクするだけです。
paercebal 2013年

3
@ボブ:[継続] ...今、リンカはコンパイラによって行われた仕事をすることができると思います、それはあなたのオプションを可能にするでしょう。(私はこれが次の標準の「モジュール」命題の主題だと思います)。Seems, they're just a pretty ugly arbitrary design.:C ++が2012年に作成された場合は、確かに。ただし、C ++は1980年代にC上に構築され、当時の制約はまったく異なっていたことを思い出してください(IIRC、採用目的でCと同じリンカーを維持することが決定されました)。
paercebal 2013年

1
@paercebal説明とメモをありがとう、paercebal!なぜそれfoo(bar)が関数であるのか確信が持てないのですか?それがポインタとして取得された場合?実際、悪いデザインと言えば、C ++ではなくCのせいです。純粋なCのいくつかの制約(ヘッダーファイルや関数が1つだけの値を返すなど)が好きではありませんが、入力で複数の引数を取ります(入力と出力が同じように動作するのは当然ではないでしょうか)。 ;なぜ複数の引数が1つの出力か?):)
Boris Burkov

1
@Bobo::Why can't I be sure, that foo(bar) is a functionfooは型である可能性があるため、クラスコンストラクターが呼び出されます。In fact, speaking of bad design, I blame C, not C++:私は多くのことでCのせいにすることができますが、70年代に設計されたことはそれらの1つではありません。繰り返しになりますが、その時間の制約... such as having header files or having functions return one and only one value:タプルは、それを緩和するのに役立ち、参照によって引数を渡すこともできます。さて、返された複数の値を取得するための構文は何でしょうか。言語を変更する価値はありますか?
paercebal 2013年

93

コンセプトの発端となったCは30年前のもので、複数のファイルからコードをリンクする唯一の実行可能な方法でした。

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


3
なぜヘッダーファイル(または実際にコンパイル/リンクに必要なもの)が単に「自動生成」されなかったのでしょうか。
Mateen Ulhaq

54

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

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


16

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


4
CからのC ++の継承が残念なのはなぜですか?
ロケシュ

3
理由は、その荷物の@Lokesh :(
陳力

1
これはどのように答えますか?
Shuvo Sarker、2018

14
@ShuvoSarkerは、何千もの言語が実証しているように、プログラマーに関数シグニチャーを2回作成させるC ++の技術的な説明がないためです。「なぜ?」への答え 「歴史」です。
ボリス

15

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

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

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


機能しない、循環参照。つまり、b.cppからb.libをビルドする前に、a.cppからa.libをビルドすることはできませんが、a.libの前にb.libをビルドすることはできません。
MSalters 2008

20
Javaはそれを解決しました、Pythonはそれを行うことができ、現代の言語はそれを行うことができます。しかし、Cが発明された当時、RAMは非常に高価で希少だったため、選択肢がありませんでした。
アーロンディグラ2008

6

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


4

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

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

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

3
ライブラリベンダーが生成された「ヘッダー」ファイルを出荷できないのはなぜですか?プリプロセッサのない「ヘッダー」ファイルを使用すると、パフォーマンスが大幅に向上するはずです(実装が本当に壊れていない限り)。
トム・ホーティン-2008

ヘッダーファイルが生成または手書きされた場合、問題は「なぜヘッダーファイルを自分で書き込むのか」ではなく、「なぜヘッダーファイルがあるのか​​」ではありません。プリプロセッサのないヘッダーについても同じことが言えます。確かに、これはより高速になります。

-5

MadKeithVの回答に応えて、

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

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

だから私たちのようなものがある場合

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

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

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

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