.hファイルには何を入れるべきですか?


93

コードを複数のファイルに分割するとき、正確に何を.hファイルに入れ、何を.cppファイルに入れるべきですか?



7
これは純粋なスタイルの問題ですが、C ++宣言は.hppファイルに入れられ、C宣言はファイルに入れられると思い.hます。これは、CコードとC ++コード(たとえば、Cのレガシーモジュール)を混在させる場合に非常に役立ちます。
Thomas Matthews

@ThomasMatthews理にかなっています。その慣習はよく使われますか?
2018年

@lightningleaf:はい、特にC ++とC言語を混在させる場合、この方法はよく使用されます。
トーマスマシューズ

回答:


113

ヘッダーファイル(.h)は、複数のファイルで必要になる情報を提供するように設計されています。クラス宣言、関数プロトタイプ、および列挙などは通常、ヘッダーファイルに格納されます。つまり、「定義」。

コードファイル(.cpp)は、1つのファイルでのみ知る必要がある実装情報を提供するように設計されています。一般に、関数本体、および他のモジュールからアクセスされるべき/決してアクセスされないであろう内部変数は、.cppファイルに属しているものです。つまり、「実装」。

「これを変更した場合、他のファイルのコードを変更して、コンパイルできるようにする必要がありますか」というのはどこにあるのかを判断するための最も簡単な質問です。答えが「はい」の場合、それはおそらくヘッダーファイルに属しています。答えが「いいえ」の場合、おそらくコードファイルに含まれています。


4
プライベートクラスのデータを除いて、ヘッダーに入力する必要があります。テンプレートは完全にヘッダー定義されている必要があります(をサポートする数少ないコンパイラーの1つを使用する場合を除くexport)。#1を回避する唯一の方法はPIMPLです。#2 exportはサポートされていれば可能であり、c ++ 0xとexternテンプレートを使用して可能である可能性があります。IMO、c ++のヘッダーファイルはその有用性の多くを失います。
KitsuneYMG 2009

23
すべて良好ですが、用語が不正確です。つまり、「宣言」-「定義」という用語は「実装」と同義です。ヘッダーには宣言型コード、インラインコード、マクロ定義、およびテンプレートコードのみを含める必要があります。つまり、コードやデータをインスタンス化するものは何もありません。
クリフォード

8
私はクリフォードに同意しなければなりません。宣言と定義という用語は、おおざっぱに、そして多少交換可能に使用します。しかし、C ++では正確な意味があります。例:クラス宣言は、クラスの名前を紹介しますが、その中に何があるかは言いません。クラス定義は、すべてのメンバーとフレンド関数をリストします。どちらも問題なくヘッダーファイルに入れることができます。「関数プロトタイプ」とは、関数宣言です。しかし、関数の定義とは、関数のコードを含むものであり、インラインまたはテンプレート(の一部)でない限り、cppファイルに配置する必要があります。
sellibitze

5
C ++では正確な意味がありますが、英語では正確な意味はありません。私の答えは後者に書かれました。
アンバー

54

事実は、C ++では、これはCヘッダー/ソース構成よりもやや複雑です。

コンパイラは何を見ますか?

コンパイラーは、ヘッダーが適切に組み込まれた1つの大きなソース(.cpp)ファイルを参照します。ソースファイルは、オブジェクトファイルにコンパイルされるコンパイル単位です。

では、なぜヘッダーが必要なのでしょうか。

あるコンパイルユニットが別のコンパイルユニットの実装に関する情報を必要とする可能性があるためです。したがって、たとえば、関数の実装を1つのソースに記述し、この関数の宣言を、それを使用する必要がある別のソースに書き込むことができます。

この場合、同じ情報のコピーが2つあります。どちらが悪だ...

解決策は、いくつかの詳細を共有することです。実装はソース内に留まる必要がありますが、関数などの共有シンボルの宣言、または構造体、クラス、列挙型などの定義を共有する必要があります。

ヘッダーは、これらの共有詳細を配置するために使用されます。

複数のソース間で共有する必要があるものの宣言をヘッダーに移動します

これ以上何もない?

C ++では、共有する必要があるため、ヘッダーに入れることができる他のものがいくつかあります。

  • インラインコード
  • テンプレート
  • 定数(通常はスイッチ内で使用したいもの...)

共有する実装を含め、共有する必要のあるすべてのものをヘッダーに移動する

次に、ヘッダー内にソースがある可能性があることを意味しますか?

はい。実際、「ヘッダー」内に存在する可能性のあるさまざまなものがあります(つまり、ソース間で共有されます)。

  • フォワード宣言
  • 関数/構造体/クラス/テンプレートの宣言/定義
  • インラインおよびテンプレートコードの実装

複雑になり、場合によっては(シンボル間の循環依存関係)、1つのヘッダーに保持することが不可能になります。

ヘッダーは3つの部分に分割できます

これは、極端な場合、次のことを意味します。

  • 前方宣言ヘッダー
  • 宣言/定義ヘッダー
  • 実装ヘッダー
  • 実装ソース

テンプレート化されたMyObjectがあるとしましょう。次のことができます。

// - - - - MyObject_forward.hpp - - - - 
// This header is included by the code which need to know MyObject
// does exist, but nothing more.
template<typename T>
class MyObject ;

// - - - - MyObject_declaration.hpp - - - - 
// This header is included by the code which need to know how
// MyObject is defined, but nothing more.
#include <MyObject_forward.hpp>

template<typename T>
class MyObject
{
   public :
      MyObject() ;
   // Etc.
} ;

void doSomething() ;

// - - - - MyObject_implementation.hpp - - - - 
// This header is included by the code which need to see
// the implementation of the methods/functions of MyObject,
// but nothing more.
#include <MyObject_declaration.hpp>

template<typename T>
MyObject<T>::MyObject()
{
   doSomething() ;
}

// etc.

// - - - - MyObject_source.cpp - - - - 
// This source will have implementation that does not need to
// be shared, which, for templated code, usually means nothing...
#include <MyObject_implementation.hpp>

void doSomething()
{
   // etc.
} ;

// etc.

うわー!

「実生活」では、通常はそれほど複雑ではありません。ほとんどのコードは、単純なヘッダー/ソース構成のみを持ち、ソースにいくつかのインラインコードがあります。

しかし、他の場合(お互いを知っているテンプレート化されたオブジェクト)では、オブジェクトごとに個別の宣言と実装ヘッダーを用意する必要がありました。

ヘッダーを個別のヘッダーに分割するもう1つの理由は、コンパイルを高速化し、構文解析するシンボルの数を必要最小限に制限し、インラインメソッドの実装が変更されたときに前方宣言のみに関心があるソースの不必要な再コンパイルを回避することです。

結論

コードの編成は、可能な限り単純化し、モジュール化する必要があります。ソースファイルにはできるだけ多く入れてください。共有する必要があるものだけをヘッダーで公開します。

しかし、テンプレート化されたオブジェクト間に循環依存がある日、コード構成がプレーンなヘッダー/ソース構成よりもいくぶん「興味深い」ものになっても驚かないでください...

^ _ ^


17

他のすべての回答に加えて、ヘッダーファイルに配置しないものを説明します。
using宣言(最も一般的なものusing namespace std;)は、それが含まれているソースファイルの名前空間を汚染するため、ヘッダーファイルに表示しないでください。 。


+1は、詳細な名前空間(または匿名の名前空間)にある限り、使用できるという警告付きです。しかし、はい、usingヘッダーのグローバル名前空間にものを取り込むために決して使用しないでください。
KitsuneYMG 2009

+1これは答えがはるかに簡単です。:)また、ヘッダーファイルには匿名の名前空間を含めないでください。
sellibitze

ヘッダーファイルに匿名の名前空間を含めることは、それが何を意味するかを理解している限り問題ありません。つまり、各翻訳単位には、名前空間で定義したものの異なるコピーが含まれます。static inlineC99では、内部リンケージとテンプレートを組み合わせるとどうなるかと関係があるため、C ++では匿名名前空間のインライン関数をお勧めします。Anon名前空間を使用すると、外部リンケージを維持しながら機能を「隠す」ことができます。
スティーブジェソップ

スティーブ、あなたが書いたものは私を納得させませんでした。ヘッダーファイルでanon名前空間が完全に理にかなっていると思う具体的な例を選んでください。
selibitze 2009

6

何もコンパイルしないもの(ゼロバイナリフットプリント)はヘッダーファイルに入ります。

変数は何もコンパイルされませんが、型宣言はコンパイルされます(変数の動作を説明するだけです)。

関数はそうしませんが、インライン関数は(またはマクロ)します。なぜなら、それらは呼び出された場所でのみコードを生成するからです。

テンプレートはコードではなく、コードを作成するためのレシピにすぎません。そのため、これらもhファイルに入れられます。


1
「インライン関数...呼び出された場所でのみコードを生成する」。それは真実ではない。インライン関数は呼び出しサイトでインライン化される場合とされない場合がありますが、インライン化されていても、実際の関数本体は非インライン関数の場合と同様に存在します。ヘッダーにインライン関数を配置しても問題ないのは、コードを生成するかどうかとは関係ありません。インライン関数は1つの定義ルールをトリガーしないため、非インライン関数とは異なり、2つの異なる翻訳単位をリンクしても問題はありません。どちらにもヘッダーが含まれています。
スティーブジェソップ

3

通常、宣言はヘッダーファイルに、定義は実装(.cpp)ファイルに配置します。これの例外はテンプレートであり、定義もヘッダーに含める必要があります。

この質問とそれに類似した質問は、SOで頻繁に尋ねられています。C++にヘッダーファイルと.cppファイルがある理由を参照してくださいおよびC ++ヘッダーファイル、たとえばコード分​​離


もちろん、ヘッダーファイルにクラス定義を含めることもできます。テンプレートである必要はありません。
selibitze 2009

1

クラスと関数の宣言とドキュメント、およびインラインの関数/メソッドの定義(別の.inlファイルに配置する方を好む場合もあります)。


1

主にヘッダーファイルにはクラスのスケルトンまたは宣言が含まれます(頻繁に変更されません)

cppファイルにはクラス実装が含まれます(頻繁に変更されます)。


5
非標準的な用語の使用はご遠慮ください。「クラススケルトン」とは何ですか、「クラス実装」とは何ですか?また、クラスのコンテキストで宣言と呼ぶものには、おそらくクラス定義が含まれます。
selibitze 2009

0

ヘッダーファイル(.h)は、クラス、構造体、そのメソッド、プロトタイプなどの宣言用である必要があります。これらのオブジェクトの実装はcppで行われます。

.h

    class Foo {
    int j;

    Foo();
    Foo(int)
    void DoSomething();
}

0

私は見ると期待します:

  • 宣言
  • コメント
  • インラインでマークされた定義
  • テンプレート

実際の答えは、何を入れないかです。

  • 定義(多重定義される可能性があります)
  • 宣言/ディレクティブを使用する(ヘッダーを含むすべてのユーザーにそれらを強制し、名前の衝突を引き起こす可能性があります)

1
もちろん、クラス定義をヘッダーファイルに含めることもできます。クラス宣言は、そのメンバーについて何も言っていません。
09

0

ヘッダーは何かを定義しますが、実装については何も伝えません。(この「メタフォア」のテンプレートを除く。

そうは言っても、「定義」をサブグループに分割する必要があります。この場合、2つのタイプの定義があります。

  • 構造の「レイアウト」を定義し、周囲の使用グループが必要とする量だけを伝えます。
  • 変数、関数、クラスの定義。

今、私はもちろん最初のサブグループについて話しています。

ヘッダーは、残りのソフトウェアが実装を使用できるようにするために、構造のレイアウトを定義するためにあります。あなたはそれをあなたの実装の「抽象化」として見たいかもしれませんが、それは大げさに言われますが、私はそれはこの場合かなりよく合うと思います。

以前のポスターでプライベートおよびパブリックの使用領域とそれらのヘッダーを宣言することについて述べ、示したように、これにはプライベートおよびパブリック変数も含まれます。ここで、ここではコードの設計については触れたくありませんが、ヘッダーに何を入れるかを検討することをお勧めします。これは、エンドユーザーと実装の間のレイヤーであるためです。


0
  • ヘッダーファイル-開発中にあまり頻繁に変更しないでください->考えて、すぐに書き込む必要があります(理想的な場合)
  • ソースファイル-実装中の変更

これは1つの方法です。いくつかの小さなプロジェクトでは、それが進むべき道かもしれません。ただし、シグネチャを変更したり削除したりするのではなく、関数とそのプロトタイプ(ヘッダーファイル内)を非推奨にすることをお勧めします。少なくともメジャー番号を変更するまでは。1.9.2が2.0.0ベータにバンプされたときのように。
TamusJRoyce 2018

0

ヘッダー(.h)

  • インターフェースに必要なマクロとインクルード(可能な限り少ない)
  • 関数とクラスの宣言
  • インターフェースのドキュメント
  • インライン関数/メソッドの宣言(ある場合)
  • グローバル変数への外部(存在する場合)

本文(.cpp)

  • 残りのマクロとインクルード
  • モジュールのヘッダーを含める
  • 関数とメソッドの定義
  • グローバル変数(存在する場合)

経験則として、モジュールの「共有」部分(他のモジュールが見ることができる必要のある部分)を.hに配置し、「非共有」部分を.cppに配置します。

PD:はい、私はグローバル変数を含めました。私は何度かそれらを使用しましたが、ヘッダーでそれらを定義しないことが重要です。そうしないと、それぞれが独自の変数を定義する多くのモジュールが得られます。

編集:デビッドのコメントの後に変更


経験則として、できるだけ少ないインクルードを.hファイルに含め、.cppファイルには必要なヘッダーを含める必要があります。これはコンパイル時間を短縮し、名前空間を汚染しません。
David Thornley、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.