C ++に別のヘッダーファイルが必要なのはなぜですか?


138

C ++が.cppファイルと同じ機能を持つ別のヘッダーファイルを必要とする理由を本当に理解したことがありません。クラスの作成とそれらのリファクタリングが非常に困難になり、プロジェクトに不要なファイルが追加されます。そして、ヘッダーファイルをインクルードする必要があるという問題がありますが、すでにインクルードされているかどうかを明示的に確認する必要があります。

C ++は1998年に承認されましたが、なぜこのように設計されているのですか?別のヘッダーファイルを持つことにはどのような利点がありますか?


フォローアップの質問:

含めるコードがすべて.hファイルの場合、コンパイラーはコードが含まれている.cppファイルをどのように見つけますか?.cppファイルが.hファイルと同じ名前であると想定していますか、それとも実際にディレクトリツリー内のすべてのファイルを調べていますか?


2
1つのファイルのみを編集する場合は、lzz(www.lazycplusplus.com)をチェックアウトします。
リチャードコーデン

回答:


105

ヘッダーファイルには他の用途もありますが、定義と宣言を分離することについて質問しているようです。

答えは、C ++はこれを「必要としない」ということです。すべてをインラインでマークすると(クラス定義で定義されたメンバー関数の場合はとにかく自動です)、分離する必要はありません。ヘッダーファイルですべてを定義できます。

あなたがかもしれない理由の欲しい分離するためには、以下のとおりです。

  1. ビルド時間を改善するため。
  2. 定義のソースなしでコードにリンクすること。
  3. すべてを「インライン」としてマークしないようにするため。

あなたのより一般的な質問が「なぜC ++はJavaと同一ではないのですか?」であるなら、私は「なぜJavaの代わりにC ++を書いているのですか?」と尋ねる必要があります。;-p

しかし、もっと真剣に、その理由は、C ++コンパイラーがjavacができるように、別の変換単位に到達してそのシンボルの使用方法を理解することができないためです。ヘッダーファイルは、リンク時に使用できると予期できるものをコンパイラーに宣言するために必要です。

したがって#include、これは単なるテキストによる置換です。ヘッダーファイルですべてを定義すると、プリプロセッサーはプロジェクト内のすべてのソースファイルの膨大なコピーと貼り付けを作成し、それをコンパイラーにフィードします。C ++標準が1998年に承認されたという事実は、これとは何の関係もありません。C++のコンパイル環境がCのコンパイル環境に非常に密接に基づいているという事実です。

フォローアップの質問に答えるために私のコメントを変換する:

コンパイラはコードが含まれている.cppファイルをどのように見つけますか

少なくとも、ヘッダーファイルを使用したコードをコンパイルするときはそうではありません。リンク先の関数は、まだ作成されている必要はありません。コンパイラーがどの.cppファイルに格納されるかを知っていることを気にしないでください。呼び出し時にコードがコンパイル時に知っておく必要のあるすべてのものは、関数宣言で表現されます。リンク時に、.oファイルまたは静的または動的ライブラリのリストを提供します。有効なヘッダーは、関数の定義がどこかにあることを約束します。


3
「分離したい理由は次のとおりです。」&ヘッダーファイルの最も重要な機能は次のとおりです。コード構造の設計を実装から分離する理由:A.多くのオブジェクトを含む非常に複雑な構造に入ると、ヘッダーファイルをふるいにかけ、それらがどのように連携するかを覚えるのははるかに簡単です。B.すべてのオブジェクト構造の定義を担当していない人が1人いて、実装を担当している人がそれを整理している。全体として、複雑なコードが読みやすくなると思います。
Andres Canella

最も簡単な方法では、ヘッダーファイルとcppファイルの分離の有用性を考えることができます。これは、中規模/大規模プロジェクトに本当に役立つインターフェイスと実装を分離することです。
クリシュナオザ2015年

(2)「定義のないコードに対してリンク」に含めることを意図しました。それでも、定義が含まれているgitリポジトリにアクセスできる可能性はありますが、重要なのは、依存関係の実装とは別にコードをコンパイルしてからリンクできることです。文字通り、インターフェース/実装を別々のファイルに分離することだけが必要で、個別にビルドする必要がない場合は、foo_interface.hにfoo_implementation.hを含めるだけでそれを行うことができます。
Steve Jessop、2015年

4
@AndresCanellaいいえ、ありません。それはあなた自身ではないコードを読んで維持することを悪夢にします。コードで何かが何をするかを完全に理解するには、n個のファイルではなく2n個のファイルをジャンプする必要があります。これはBig-Oh表記ではありません。2nは、nと比べて多くの違いをもたらします。
BłażejMichalik

1
ヘッダーが役立つ嘘であることを私は第二に述べた。たとえば、minixソースを確認してください。制御が渡される場所から、宣言/定義される場所をたどるのは非常に困難です。分離された動的モジュールを介して構築された場合、1つのことを理解してからジャンプすると、依存関係モジュール。代わりに、ヘッダーに従う必要があり、この方法で記述されたコードを地獄で読むことになります。対照的に、nodejsはifdefなしで何がどこから来たかを明確にし、何がどこから来たかを簡単に識別できます。
Dmitry

90

C ++は、Cがそのようにしたのでそのようにしています。本当の問題は、なぜCがそのようにしたのですか?ウィキペディアはこれについて少し話します。

新しいコンパイル言語(Java、C#など)は、前方宣言を使用しません。識別子はソースファイルから自動的に認識され、動的ライブラリシンボルから直接読み取られます。つまり、ヘッダーファイルは必要ありません。


12
+1頭の釘を打ちます。これは本当に詳細な説明を必要としません。
MSalters 2009

6
それは頭に釘を打たなかった:(私はまだC ++がフォワード宣言を使用しなければならない理由と、なぜそれがソースファイルから識別子を認識できず、ダイナミックライブラリシンボルから直接読み取れないのか、そしてなぜC ++がそれをしたのかを調べなければならないCがそのようにしたからといって:p
Alexander Taylor

2
@AlexanderTaylor :)
ドナルドバード

66

一部の人々はヘッダーファイルを利点と考えています:

  • インターフェースと実装の分離を有効/強制/許可すると主張されていますが、通常はそうではありません。ヘッダーファイルは実装の詳細でいっぱいであり(たとえば、クラスのメンバー変数は、パブリックインターフェイスの一部ではない場合でも、ヘッダーで指定する必要があります)、関数は、クラス宣言でインライン定義できます。ヘッダーで、この分離を再び破壊します。
  • 各翻訳単位は個別に処理できるため、コンパイル時間を改善すると言われることがあります。それでも、コンパイル時間に関しては、C ++はおそらく最も遅い言語です。その理由の1つは、同じヘッダーが何度も繰り返しインクルードされることです。多数のヘッダーが複数の変換ユニットに含まれているため、それらを複数回解析する必要があります。

最終的に、ヘッダーシステムは、Cが設計された70年代の成果物です。当時、コンピュータにはメモリがほとんどなかったため、モジュール全体をメモリに保持することは不可能でした。コンパイラは最初からファイルの読み取りを開始し、ソースコードを直線的に処理する必要がありました。これを可能にするのがヘッダーメカニズムです。コンパイラーは他の変換単位を考慮する必要はありません。コードを上から下に読み取るだけです。

また、C ++は下位互換性のためにこのシステムを保持していました。

今日、それは意味がありません。非効率的でエラーが発生しやすく、複雑すぎます。それが目標あった場合、インターフェースと実装を分離するはるかに良い方法があります。

ただし、C ++ 0xの提案の1つは、適切なモジュールシステムを追加して、コードを.NETまたはJavaのように、より大きなモジュールに、一度にヘッダーなしでコンパイルできるようにすることでした。この提案はC ++ 0xを削減するものではありませんでしたが、「後でこれを実行したい」というカテゴリーにはまだ残っていると思います。おそらくTR2か類似のものです。


これがこのページのベストアンサーです。ありがとうございました!
チャックルバット

29

私の(限られた-通常はC開発者ではない)理解では、これはCに根ざしています。Cはクラスまたは名前空間が何であるかを知らないことに注意してください。これは1つの長いプログラムです。また、使用する前に関数を宣言する必要があります。

たとえば、次の場合はコンパイラエラーが発生します。

void SomeFunction() {
    SomeOtherFunction();
}

void SomeOtherFunction() {
    printf("What?");
}

エラーは、宣言の前に呼び出すため、「SomeOtherFunctionは宣言されていません」である必要があります。これを修正する1つの方法は、SomeOtherFunctionをSomeFunctionの上に移動することです。別のアプローチは、最初に関数のシグネチャを宣言することです:

void SomeOtherFunction();

void SomeFunction() {
    SomeOtherFunction();
}

void SomeOtherFunction() {
    printf("What?");
}

これにより、コンパイラーは次のことを知ることができます。コードのどこかに、SomeOtherFunctionという名前の関数があり、これはvoidを返し、パラメーターを受け取りません。そのため、SomeOtherFunctionを呼び出そうとするコードを検出した場合、パニックにならずに、代わりにコードを探してください。

ここで、2つの異なる.cファイルにSomeFunctionとSomeOtherFunctionがあるとします。次に、Some.cに#SomeOther.cを含める必要があります。次に、いくつかの「プライベート」関数をSomeOther.cに追加します。Cはプライベート関数を知らないので、その関数はSome.cでも使用できます。

これが.hファイルの出所です。他の.cファイルからアクセスできる.cファイルから「エクスポート」するすべての関数(および変数)を指定します。そうすることで、Public / Privateスコープのようなものが得られます。また、ソースコードを共有しなくても、この.hファイルを他の人に渡すことができます。.hファイルは、コンパイルされた.libファイルに対しても機能します。

したがって、主な理由は、本当に便利で、ソースコードを保護し、アプリケーションの部分を少し分離するためです。

それはCでした。C ++はクラスとプライベート/パブリック修飾子を導入したので、それらが必要かどうかを尋ねることはできますが、C ++ AFAIKはそれらを使用する前に関数の宣言を必要とします。また、多くのC ++開発者はCデベロッパーでもあり、Cデベロッパーでもあり、その概念と習慣をC ++に引き継ぎました。


5
コンパイラがコードを実行してすべての関数定義を見つけることができないのはなぜですか?コンパイラにプログラムするのはかなり簡単なことのようです。
マリウス

3
あなたソースを持っているなら、あなたしばしば持っていません。コンパイルされたC ++は、コードをロードしてリンクするのに十分な追加情報を備えた、効果的なマシンコードです。次に、CPUをエントリポイントに向け、実行させます。これは、コードがコンテンツのメタデータを含む中間バイトコードにコンパイルされるJavaまたはC#とは根本的に異なります。
DevSolar 2009

私は、1972年に、コンパイラーにとってかなり高価な操作であったと思います。
Michael Stum

まさにマイケル・スタンが言ったこと。この動作が定義されたとき、単一の変換ユニットを介した線形スキャンは、実際に実装できる唯一のものでした。
jalf

3
うん-大量のテープを使用して16ビターでコンパイルするのは簡単ではありません。
MSalters 2009

11

最初の利点:ヘッダーファイルがない場合は、他のソースファイルにソースファイルを含める必要があります。これにより、インクルードファイルが変更されたときに、インクルードファイルが再度コンパイルされます。

2番目の利点:異なるユニット(異なる開発者、チーム、会社など)間でコードを共有することなく、インターフェースを共有できます。


1
たとえば、C#では「他のソースファイルにソースファイルを含める必要があります」という意味ですか?明らかにそうではないからです。2番目の利点として、言語に依存しすぎると思います。たとえば、Delphiで.hファイルを使用しない
Vlagged

とにかくプロジェクト全体を再コンパイルする必要があるので、最初の利点は本当に重要ですか?
マリウス

わかりましたが、言語機能はありません。「問題」を定義する前に、C宣言を処理する方が実用的です。それは有名人が「それは機能のバグではない」と言っているようです:)
ニューロ

@マリウス:はい、本当に重要です。プロジェクト全体をリンクすることは、プロジェクト全体をコンパイルしてリンクすることとは異なります。プロジェクト内のファイルの数が増えると、それらすべてをコンパイルすると本当に面倒になります。@Vlagged:その通りですが、C ++を他の言語と比較していません。ソースファイルのみを使用した場合と、ソースおよびヘッダーファイルを使用した場合を比較しました。
erelender 2009

C#には他のソースファイルは含まれていませんが、モジュールを参照する必要があります。これにより、コンパイラはソースファイルをフェッチ(またはバイナリに反映)して、コードが使用するシンボルを解析します。
gbjbaanb 2009

5

ヘッダーファイルの必要性は、コンパイラーが他のモジュールの関数や変数の型情報について知るための制限から生じます。コンパイルされたプログラムまたはライブラリーには、他のコンパイル単位で定義されたオブジェクトにバインドするためにコンパイラーが必要とするタイプ情報は含まれていません。

この制限を補うために、CおよびC ++は宣言を許可し、これらの宣言は、プリプロセッサーの#includeディレクティブの助けを借りてそれらを使用するモジュールに含めることができます。

一方、JavaやC#などの言語では、バインディングに必要な情報がコンパイラの出力(クラスファイルまたはアセンブリ)に含まれています。したがって、モジュールのクライアントが含めるスタンドアロン宣言を維持する必要がなくなりました。

バインディング情報がコンパイラーの出力に含まれない理由は単純です。実行時に必要ないためです(型チェックはコンパイル時に行われます)。それはスペースを無駄にするだけです。C / C ++は、実行可能ファイルまたはライブラリのサイズがかなり重要であった時期から来ていることを思い出してください。


仰るとおりです。私はここで同様のアイデアを得た:stackoverflow.com/questions/3702132/...
smwikipedia

4

C ++は、特に言語そのものではないCについて不必要に何も変更することなく、Cインフラストラクチャに最新のプログラミング言語機能を追加するように設計されました。

はい、この時点(最初のC ++標準から10年後、使用量が大幅に増加し始めてから20年後)では、適切なモジュールシステムがない理由を簡単に確認できます。明らかに、今日設計されている新しい言語はC ++のようには機能しません。しかし、それがC ++のポイントではありません。

C ++のポイントは、進化的で、既存のプラクティスをスムーズに継続し、ユーザーコミュニティに適切に機能するものを(頻繁に)壊すことなく新しい機能を追加することです。

これは、他の言語よりも困難なこと(特に新しいプロジェクトを始める人にとって)と、簡単なこと(特に既存のコードを保守する人にとって)を意味します。

したがって、C ++がC#になることを期待するのではなく(既にC#があるので無意味です)、ジョブに適したツールを選んでみませんか?私自身、現代の言語で新しい機能のかなりの部分を書くように努めており(たまたまC#を使用しています)、C ++で保持している既存のC ++を大量に持っています。すべて。とにかく、それらは非常にうまく統合されているので、ほとんど痛みはありません。


C#とC ++をどのように統合しますか?COMを通じて?
Peter Mortensen

1
主な方法は3つあり、「最良」は既存のコードに依存します。3つすべて使用しました。私が最もよく使用するのはCOMです。これは、既存のコードが既にその周りで設計されているため、実質的にシームレスで、非常にうまく機能します。奇妙な場所で私はC ++ / CLIを使用します。これにより、COMインターフェースがまだない場合に非常にスムーズに統合できます(既存のCOMインターフェースを使用している場合でも、使用する方が好ましい場合があります)。最後に、基本的にDLLから公開されたCのような関数を呼び出すことができるp / invokeがあり、C#から任意のWin32 APIを直接呼び出すことができます。
Daniel Earwicker、2009

4

まあ、C ++は1998年に批准されましたが、それよりもずっと長く使用されており、批准は主に構造を課すのではなく、現在の使用法を制定することでした。また、C ++はCに基づいており、Cにはヘッダーファイルがあるため、C ++にもヘッダーファイルがあります。

ヘッダーファイルの主な理由は、ファイルの個別のコンパイルを可能にし、依存関係を最小限に抑えるためです。

foo.cppがあり、bar.h / bar.cppファイルのコードを使用したいとします。

foo.cppに「bar.h」を#includeして、bar.cppが存在しない場合でもfoo.cppをプログラムしてコンパイルできます。ヘッダーファイルは、bar.hのクラス/関数が実行時に存在することをコンパイラーに約束する役割を果たし、すでに知っている必要があるすべてのものを持っています。

もちろん、プログラムをリンクしようとしたときにbar.hの関数に本体がない場合は、リンクされず、エラーが発生します。

副作用として、ソースコードを明かさずにヘッダーファイルをユーザーに提供できます。

もう1つは、*。cppファイルでコードの実装を変更してもヘッダーをまったく変更しない場合は、それを使用するすべてのものではなく、*。cppファイルをコンパイルするだけでよいということです。もちろん、ヘッダーファイルに多くの実装を含めると、これはあまり役に立ちません。


3

mainと同じ機能を持つ別のヘッダーファイルは必要ありません。複数のコードファイルを使用してアプリケーションを開発し、以前に宣言されていない関数を使用する場合にのみ必要です。

それは本当にスコープの問題です。


1

C ++は1998年に承認されましたが、なぜこのように設計されているのですか?別のヘッダーファイルを持つことにはどのような利点がありますか?

実際にヘッダーファイルは、プログラムを初めて検査するときに非常に役立ちます。ヘッダーファイルをチェックアウトすると(テキストエディターのみを使用して)、クラスを表示するために高度なツールを使用する必要がある他の言語とは異なり、プログラムのアーキテクチャの概要がわかります。それらのメンバー関数。


1

ヘッダーファイルの背後にある本当の(歴史的な)理由は、コンパイラーの開発者にとってより簡単になったと思いますが、ヘッダーファイルに利点があります。
詳細については、この以前の投稿を確認してください...


1

ヘッダーファイルがなくても、C ++を完全に開発できます。実際、テンプレートを集中的に使用する一部のライブラリは、ヘッダー/コードファイルパラダイムを使用しません(boostを参照)。しかし、C / C ++では、宣言されていないものは使用できません。これに対処する1つの実用的な方法は、ヘッダーファイルを使用することです。さらに、コード/実装を共有せずにインターフェイスを共有できるという利点があります。そして、私はそれがCの作成者によって想定されていなかったと思います:共有ヘッダーファイルを使用するときは、有名なものを使用する必要があります。

#ifndef MY_HEADER_SWEET_GUARDIAN
#define MY_HEADER_SWEET_GUARDIAN

// [...]
// my header
// [...]

#endif // MY_HEADER_SWEET_GUARDIAN

これは実際には言語機能ではありませんが、複数の包含を処理する実際的な方法です。

したがって、Cが作成されたとき、前方宣言の問題は過小評価されていたと思います。C++のような高水準言語を使用するときは、このようなことに対処する必要があります。

貧しいC ++ユーザーにとってのもう1つの負担...


1

コンパイラーが他のファイルで定義されたシンボルを自動的に検出するようにしたい場合は、プログラマーにそれらのファイルを事前定義された場所に置くように強制する必要があります(Javaパッケージ構造がプロジェクトのフォルダー構造を決定するなど)。私はヘッダーファイルを好みます。また、使用するライブラリのソース、またはコンパイラーが必要とする情報をバイナリーに入れるための一定の方法が必要です。

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