.cppファイルのみを含めるとすべてが機能するのに、なぜ.hを含める必要があるのですか?


18

ファイルを含めるだけで機能させることができるのに、なぜファイル.h.cppファイルの両方を含める必要があるの.cppですか?

例:file.h包含宣言を作成し、次にfile.cpp包含定義を作成し、両方を包含しmain.cppます。

または、file.cppを含む包含宣言/定義(プロトタイプなし)を作成しmain.cppます。

両方とも私のために働く。違いがわかりません。コンパイルとリンクのプロセスに関する何らかの洞察が役立つかもしれません。


あなたの研究を共有することは皆を助けます。試したことと、それがニーズに合わなかった理由を教えてください。これは、あなたが時間をかけて自分自身を助けようとしていること、明白な答えを繰り返すことから私たちを救うこと、そして何よりもあなたがより具体的で関連性のある答えを得ることを助けることを示しています。参照してください掲載する方法
GNAT

関連する質問をこちらでご覧になることを強くお勧めします。Programmers.stackexchange.com/questions/56215/…およびProgrammers.stackexchange.com/questions/115368/…は良い読み物です

なぜSOではなくプログラマーにこれがありますか?
モニカとの軽さレース14

@LightnessRacesInOrbitは、コーディング方法の問題であり、「ハウツー」の質問ではないためです。
CashCow 14

@CashCow:SOを誤解しているように聞こえますが、これは「ハウツー」サイトではありません。また、コーディングスタイルの問題ではなく、この質問を誤解しているように聞こえます。
モニカとの軽さのレース14

回答:


29

あなた.cpp言及したようにファイルを含めることができます、これは悪い考えです。

前述のように、宣言はヘッダーファイルに属します。これらは、実装が含まれていないため、複数のコンパイル単位に含まれていても問題はありません。関数またはクラスメンバーの定義を複数回含めると、通常は問題が発生します(常にではありません)。これは、リンカーが混乱してエラーをスローするためです。

起こるべきことは、各.cppファイルには、クラス、論理的に編成された関数のグループ、グローバルな静的変数(あるとしても控えめに使用する)など、プログラムのサブセットの定義が含まれていることです。

コンパイル単位.cppファイル)には、含まれる定義をコンパイルするために必要な宣言が含まれます。参照する関数とクラスを追跡しますが、含まれないため、リンカはオブジェクトコードを実行可能ファイルまたはライブラリに結合するときにそれらを後で解決できます。

  • Foo.h ->クラスFooの宣言(インターフェイス)が含まれます。
  • Foo.cpp -> Fooクラスの定義(実装)が含まれています。
  • Main.cpp-> mainメソッド、プログラムエントリポイントが含まれます。このコードはFooをインスタンス化し、それを使用します。

どちらFoo.cppMain.cpp含む必要性Foo.hFoo.cppクラスインターフェイスをサポートするコードを定義しているため、そのインターフェイスが何であるかを知る必要があるため、これが必要です。Main.cppFooを作成してその動作を呼び出すため、それが必要です。そのため、その動作とは何か、メモリ内のFooのサイズ、その機能を見つける方法などを知る必要がありますが、実際の実装はまだ必要ありません。

コンパイラが生成されますFoo.oから、Foo.cppコンパイルされた形式でのFooクラスのすべてのコードが含まれています。またMain.o、mainメソッドとクラスFooへの未解決の参照を含むものも生成します。

今、2つのオブジェクト・ファイルを結合したリンカー、来るFoo.oMain.o実行可能ファイルには。未解決のFoo参照Main.oは表示されFoo.oますが、必要なシンボルが含まれているため、いわば「点をつなぐ」のです。関数呼び出しMain.oは、コンパイルされたコードの実際の場所に接続されるようになったため、実行時にプログラムは正しい場所にジャンプできます。

Foo.cppファイルをに含めた場合、クラスFooには2つの定義Main.cppがあります。リンカはこれを見て、「どれを選ぶべきかわからないので、これはエラーです」と言います。コンパイル手順は成功しますが、リンクは成功しません。(コンパイルしない場合を除き、なぜ別のファイルに保存されるのですか?)Foo.cpp.cpp

最後に、さまざまなファイルタイプの概念は、C / C ++コンパイラとは無関係です。希望する言語の有効なコードを含む「テキストファイル」をコンパイルします。ファイル拡張子に基づいて言語を伝えることができる場合があります。たとえば、.cコンパイラオプションを使用せずにファイルをコンパイルすると、Cが想定されますが、拡張子.ccまたは.cpp拡張子はC ++を想定するように指示します。しかし、私は簡単にコンパイルするコンパイラに伝えることができ.h、さらには.docxC ++などのファイルを、それがオブジェクト(放出する.oことがプレーンテキスト形式で有効なC ++コードが含まれている場合)ファイルを。これらの拡張機能は、プログラマの利益のためにあります。私が見た場合Foo.hFoo.cpp、私はすぐにファーストクラスの宣言が含まれており、第二の定義が含まれていることを前提としています。


ここであなたがしている議論を理解していません。あなただけ含まれている場合Foo.cppにはMain.cpp、あなたがする必要はありません.hファイルを、あなたはまだ読みやすくするために別々のファイルに分割するコードで勝つ、1つの以下のファイルを持っており、あなたのコンパイルコマンドが簡単です。私は理解しますが、ヘッダーの重要性は、私はこれがそれだとは思わないファイル
ベンジャミンGruenbaum

2
簡単なプログラムの場合は、すべてを1つのファイルに入れてください。プロジェクトヘッダー(ライブラリヘッダーのみ)も含めないでください。ソースファイルを含めることは悪い習慣であり、複数のコンパイルユニットがあり、実際にそれらを個別にコンパイルすると、最小のプログラム以外で問題が発生します。

2
If you had included the Foo.cpp file in Main.cpp, there would be two definitions of class Foo.質問に関する最も重要な文。
マルツェルム14

@Snowman Right、これはあなたが注目すべきだと思うことです-複数のコンパイルユニット。コードを小さな断片に分割するためにヘッダーファイルの有無に関係なくコードを貼り付けることは、ヘッダーファイルの目的に役立ちます。ヘッダーファイルをファイルに分割するだけであれば、何も購入しません。動的リンク、条件付きリンク、およびより高度なビルドが必要になると、それらは突然非常に便利になります。
ベンジャミングリュンバウム14

C / C ++コンパイラ/リンカーのソースを整理する方法はたくさんあります。質問者はプロセスが不確かだったので、コードを整理する方法とプロセスがどのように機能するかのベストプラクティスを説明しました。コードを別の方法で整理することは可能ですが、99%のプロジェクトがそれを行う方法を説明しました。それはうまく機能し、あなたの正気を維持します。

9

概念的にはCまたはC ++コンパイラーの最初の「フェーズ」であるCおよびC ++プリプロセッサーの役割について詳しく読んでください(これまでは別個のプログラム/lib/cppでした。現在、パフォーマンス上の理由から、コンパイラー自体cc1 またはに統合されていますcc1plus)。特にGNU cppプリプロセッサのドキュメントをお読みください。そのため、実際には、コンパイラは概念的に最初にコンパイル単位(または変換単位)を前処理してから、前処理されたフォームで作業します。

ヘッダーファイルに次の内容が含まれている場合は、常にヘッダーファイルを含める必要がありfile.hます(規則習慣によって決まります)。

  • マクロ定義
  • タイプの定義(例えばtypedefstructclassなど、...)
  • static inline関数の定義
  • 外部関数の宣言。

これらをヘッダーファイルに配置することは、慣例(および利便性)の問題であることに注意してください。

もちろん、あなたの実装file.cppは上記のすべてを必要とするので#include "file.h" 、最初はそうしたいです。

これは慣例です(ただし、非常に一般的なものです)。ヘッダーファイルを回避し、その内容をコピーして実装ファイル(つまり、翻訳単位)に貼り付けることができます。しかし、それは望ましくありません(おそらく、CまたはC ++コードが自動的に生成される場合を除いて、ジェネレータプログラムにそのコピー&ペーストを行わせ、プリプロセッサの役割を模倣することができます)。

ポイントは、プリプロセッサがテキストのみの操作を実行していることです。(原則として)コピー&ペーストで完全に回避するか、別の「プリプロセッサ」またはCコードジェネレーター(gppm4など)で置き換えることができます。

追加の問題は、最近のC(またはC ++)標準が複数の標準ヘッダーを定義していることです。ほとんどの実装では、実際にこれらの実装標準(実装固有)などのヘッダファイルを、私は準拠した実装は標準実装することが(のような含みことが可能であろうと信じている#include <stdio.h>Cのために、または#include <vector>C ++用)など、いくつかのマジックのトリックと(いくつかのデータベースまたは一部を使用してコンパイラの情報)。

使用している場合のGCCコンパイラ(例えばgccまたはg++)をあなたが使用することができ-H、すべてのインクルージョンについての情報を取得するフラグを、そして-C -Eフラグが前処理されたフォームを得ること。もちろん、前処理に影響する他の多くのコンパイラフラグがあります(たとえば-I /some/dir//some/dir/インクルードファイルの検索用に追加したり、-D一部のプリプロセッサマクロなどを事前定義したりするなど)。

NB。C ++の将来のバージョン(おそらくC ++ 20、おそらくそれ以降)にはC ++モジュールが含まれる可能性があります


1
リンクが腐敗した場合でも、これは良い答えでしょうか?
ダンピチェルマン14

4
回答を改善するために編集し、ウィキペディアまたはGNUドキュメントへのリンクを慎重に選択します。
バジルStarynkevitch 14

3

C ++の複数ユニットビルドモデルのため、プログラムに1回だけ表示されるコード(定義)が必要であり、プログラムの各翻訳ユニットに表示されるコード(宣言)が必要です。

これからC ++ヘッダーのイディオムが生まれます。それには理由があります。

あなたはできる、単一の翻訳単位にあなたのプログラム全体をダンプしますが、コードの再利用、ユニットテストとモジュール間の依存関係の扱いで、この紹介の問題。それはただの大きな混乱でもあります。


0

なぜヘッダーファイルを記述する必要があるのかという選択した回答 合理的な説明ですが、詳細を追加したかったのです。

ヘッダーファイルの合理性は、C / C ++の教育と議論で失われる傾向があるようです。

ヘッダーファイルは、2つのアプリケーション開発の問題に対する解決策を提供します。

  1. インターフェースと実装の分離
  2. 大規模プログラムのコンパイル/リンク時間の改善

C / C ++は、小さなプログラムから非常に大きな数百万行、数千のファイルプログラムまで拡張できます。アプリケーション開発は、1人の開発者のチームから数百人の開発者にまで拡大できます。

開発者としていくつかの帽子をかぶることができます。特に、関数とクラスへのインターフェイスのユーザーになるか、関数とクラスのインターフェイスの作成者になることができます。

関数を使用している場合、関数インターフェース、使用するパラメーター、関数が返すもの、および関数が何を行うかを知る必要があります。これは、実装を見ることなく、ヘッダーファイルに簡単に文書化されます。の実装をprintfいつ読みましたか?私たちは毎日それを使用して購入します。

あなたがインターフェースの開発者であるとき、帽子は他の方向を変えます。ヘッダーファイルは、パブリックインターフェイスの宣言を提供します。ヘッダーファイルは、このインターフェイスを使用するために別の実装が必要とするものを定義します。この新しいインターフェイスの内部およびプライベート情報は、ヘッダーファイルで宣言されていません(宣言すべきではありません)。パブリックヘッダーファイルは、モジュールを使用するために必要なすべてのファイルである必要があります。

大規模な開発では、コンパイルとリンクに時間がかかる場合があります。数分から数時間(偶数から数日まで!)ソフトウェアをインターフェース(ヘッダー)と実装(ソース)に分割すると、すべてを再構築するのではなく、コンパイルする必要があるファイルのみをコンパイルする方法が提供されます。

さらに、ヘッダーファイルを使用すると、開発者はライブラリ(既にコンパイル済み)とヘッダーファイルを提供できます。ライブラリの他のユーザーは、適切な実装を見ることはないかもしれませんが、ヘッダーファイルでライブラリを使用できます。これは、C / C ++標準ライブラリを使用して毎日行います。

小さなアプリケーションを開発している場合でも、大規模なソフトウェア開発手法を使用するのは良い習慣です。しかし、これらの習慣を使用する理由も覚えておく必要があります。


0

同様に、すべてのコードを1つのファイルに入れない理由を尋ねることもできます。

最も簡単な答えはコードのメンテナンスです。

クラスを作成するのが妥当な場合があります。

  • ヘッダー内にすべてインライン
  • すべてコンパイル単位内にあり、ヘッダーはまったくありません。

ヘッダーに完全にインライン化するのが妥当なのは、クラスが実際にいくつかの基本的なゲッターとセッターと、おそらくメンバーを初期化するための値を取るコンストラクターを持つデータ構造体であるときです。

(すべてインラインにする必要があるテンプレートは、わずかに異なる問題です)。

ヘッダー内にすべてクラスを作成するもう1つの場合は、複数のプロジェクトでクラスを使用する可能性があり、特にライブラリでのリンクを回避する必要がある場合です。

コンパイル単位内にクラス全体を含め、ヘッダーをまったく公開しない場合は次のとおりです。

  • 実装するクラスによってのみ使用される「impl」クラス。それはそのクラスの実装の詳細であり、外部では使用されません。

  • 基本クラスへのポインター/参照/スマートポインターを返す何らかの種類のファクトリメソッドによって作成される抽象基本クラスの実装。ファクトリメソッドはクラス自体によって公開され、公開されません。(さらに、クラスが静的インスタンスを介してテーブルに登録するインスタンスを持っている場合、ファクトリを介して公開する必要さえありません)。

  • 「ファンクター」タイプのクラス。

つまり、誰にもヘッダーを含めたくない場合。

あなたが何を考えているのか知っています... cppファイル(または完全にインライン化されたヘッダー)をインクルードすることにより、メンテナンス性のためだけであれば、ファイルを簡単に編集してコードを「見つけ」、再構築することができます。

ただし、「保守性」とは、コードが整然と見えることだけではありません。それは変化の影響の問題です。ヘッダーを変更せずに実装(.cpp)ファイルを変更するだけであれば、副作用がないため、他のソースを再構築する必要はないことが一般的に知られています。

これにより、ノックオン効果を心配せずにそのような変更を行うことが「安全」になります。それが実際に「維持可能性」とは何を意味するかです。


-1

Snowmanの例では、.hファイルが必要な理由を正確に示すために、小さな拡張子が必要です。

クラスFooにも依存する別のクラスバーをプレイに追加します。

Foo.h-> Fooクラスの宣言を含む

Foo.cpp->クラスFooの定義(実装)を含む

Main.cpp-> Foo型の変数を使用します。

Bar.cpp-> Foo型の変数も使用します。

ここで、すべてのcppファイルにFoo.hを含める必要があります。他の複数のcppファイルにFoo.cppを含めるとエラーになります。クラスFooが複数回定義されるため、リンカーは失敗します。


1
それもそうではありません。それは.hとにかくファイルで解決されるのとまったく同じ方法で解決できます- インクルードガード.h使用すると、とにかくあなたがファイルで持っているのとまったく同じ問題です。これは.hファイルの目的ではありません。
ベンジャミングリュンバウム14

(preprocessodのように)ファイルごとにしか機能しないため、どのようにインクルードガードを使用するのかわかりません。この場合、Main.cppとBar.cppは異なるファイルであるため、Foo.cppは両方に1回だけ含まれます(保護されていてもいなくても違いはありません)。Main.cppとBar.cppは両方ともコンパイルされます。ただし、クラスFooの二重定義が原因でリンクエラーが発生します。ただし、言及していなかった継承もあります。宣言の外部モジュール(DLLの)などのために
SkaarjFace

いいえ、1つのコンパイル単位になり.cppます。ファイルをインクルードするため、リンクはまったく行われません。
ベンジャミングリュンバウム14

コンパイルしないとは言わなかった。ただし、同じ実行可能ファイルのmain.oとbar.oの両方をリンクすることはありません。リンクせずにコンパイルのポイントは何ですか。
SkaarjFace

うーん...実行するコードを取得していますか?リンクすることはできますが、ファイルを相互にリンクすることはできません-むしろ同じコンパイル単位になります。
ベンジャミングリュンバウム14

-2

同じファイルにコード全体を記述すると、コードが見苦しくなります。
第二に、書かれたクラスを他の人と共有することはできません。ソフトウェアエンジニアリングによると、クライアントコードを個別に記述する必要があります。クライアントは、プログラムがどのように機能しているかを知らないはずです。出力が必要なのは、同じファイルにプログラム全体を書き込むと、プログラムのセキュリティがリークするためです。

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