ヘッダーファイルにC ++定義を配置することは良い習慣ですか?


196

C ++での私の個人的なスタイルでは、常にクラス宣言をインクルードファイルに、定義を.cppファイルに配置する必要があります。これは、C ++ヘッダーファイルに対するLokiの回答、コード分離で規定されているものと非常によく似ています。確かに、このスタイルが好きな理由の1つは、Modula-2とAdaのコーディングに費やしたすべての年に関係していると考えられます。

私よりもC ++に精通している同僚がいます。その同僚は、可能な場合はすべてのC ++宣言に定義をヘッダーファイルに含めるべきだと主張しています。彼はこれが有効な代替スタイルであると言ったり、わずかに優れたスタイルであるとは言っていませんが、むしろこれは誰もが現在C ++に使用している、広く受け入れられている新しいスタイルです。

私は以前ほどのぼんやりしているわけではないので、彼と一緒に何人か他の人がいるのを見るまで、彼のこのバンドワゴンに引っかかるのを本当に心配していません。それで、このイディオムは本当にどれほど一般的ですか?

ただ、答えにいくつかの構造を与えるために:それは今の道は珍しく、やや一般的な、非常に一般的な、またはバグアウトクレイジー?


ヘッダー内の1行の関数(ゲッターとセッター)は一般的です。不思議な一見を得るよりも長い。おそらく、同じヘッダーの別のクラスによってのみ使用される小さなクラスの完全な定義のために?
Martin Beckett、

私はこれまで、すべてのクラス定義を常にヘッダーに入れてきました。例外は、pimplクラスの定義だけです。ヘッダーでのみ宣言します。
ヨハネスシャウブ-litb 2009

3
たぶん彼はその方法を考えているのは、Visual C ++がコードの記述を主張しているからです。ボタンをクリックすると、ヘッダーファイルに実装が生成されます。他の人が以下で説明している理由のために、なぜマイクロソフトがこれを奨励するのか私にはわかりません。
WKS 2012年

4
@WKS-MicrosoftはむしろC#ですべての人がプログラムを行うことを望んでいます。C#では、「ヘッダー」と「本体」の区別はなく、1つのファイルにすぎません。C ++とC#の両方の世界で長い間使用されてきたため、実際にはC#の方がはるかに扱いやすくなっています。
Mark Lakata、2015

1
@MarkLakata-それは確かに彼が指摘したことの1つです。私は最近彼のうち、この引数を聞いていないが、IIRC、彼はJavaとC#の作業こうして、およびC#は、すべての言語がすぐに、次のことだろう、それトレンド作られた時間、で真新しいだったと主張した
TED

回答:


209

あなたの同僚は間違っています。一般的な方法は、コードを.cppファイル(または任意の拡張子)に入れ、宣言をヘッダーに入れることです。

ヘッダーにコードを配置することにはメリットがある場合がありますが、これにより、コンパイラーがより巧妙なインライン化を行うことができます。ただし、同時に、すべてのコードはコンパイラーに組み込まれるたびに処理する必要があるため、コンパイル時間が破壊される可能性があります。

最後に、すべてのコードがヘッダーである場合に、オブジェクトの循環関係(場合によっては望ましい)を持つことはしばしば煩わしいものです。

結論、あなたは正しかった、彼は間違っている。

編集:私はあなたの質問について考えてきました。彼の言うことが真実であるケースが1つあります。テンプレート。boostなどの新しい「モダン」ライブラリの多くはテンプレートを多用し、多くの場合「ヘッダーのみ」です。ただし、これはテンプレートを処理するときにのみ行う必要があります。テンプレートを処理するときにそれを行う唯一の方法だからです。

編集:一部の人々はもう少し明確化を望みます、ここに「ヘッダーのみ」のコードを書くことの欠点についてのいくつかの考えがあります:

周りを検索すると、boostを処理するときにコンパイル時間を短縮する方法を見つけようとする人がかなりたくさんいます。例:Boost Asioを使用してコンパイル時間を短縮する方法。これには、boostが含まれた単一の1Kファイルの14秒のコンパイルが表示されます。14は「爆発」しているように見えないかもしれませんが、それは確かに通常よりもはるかに長く、非常にすぐに加算されます。大規模なプロジェクトを扱う場合。ヘッダーのみのライブラリは、かなり測定可能な方法でコンパイル時間に影響します。ブーストはとても便利なので、私たちはそれを許容します。

さらに、ヘッダーだけでは実行できないことがたくさんあります(boostには、スレッドやファイルシステムなどの特定の部分にリンクする必要があるライブラリがあります)。主な例は、複数の定義エラーが発生するため、ヘッダーのみのライブラリに単純なグローバルオブジェクトを含めることができないことです(シングルトンであるabominationに頼らない限り)。注: C ++ 17のインライン変数により、この特定の例は将来的に実行可能になります。

最後のポイントとして、ヘッダーのみのコードの例としてブーストを使用すると、多くの場合、詳細が見落とされます。

Boostはライブラリであり、ユーザーレベルのコードではありません。それほど頻繁には変更されません。ユーザーコードでは、すべてをヘッダーに入れると、少しの変更でもプロジェクト全体を再コンパイルする必要があります。これは、時間の浪費であり、(コンパイルごとに変化しないライブラリの場合はそうではありません)。ヘッダー/ソースとより良いものに分割し、フォワード宣言を使用してインクルードを削減すると、1日で合計すると、再コンパイルの時間を節約できます。


16
私はそれが彼がそれを手に入れているところだと確信しています。これが出てくるときはいつでも、彼はテンプレートを持ち出します。彼の主張は、おおまかに言って一貫性を保つためにすべてのコードをこのようにすべきだということです。
TED

14
それは彼がしている悪い議論です、あなたの銃に固執してください:)
Evan Teran

11
「export」キーワードがサポートされている場合は、テンプレート定義をCPPファイルに含めることができます。これは、私の知る限りでは、ほとんどのコンパイルで通常は実装されないC ++の暗い部分です。
Andrei Taranchenko、

2
:たとえば、この回答の底部(上部がやや畳み込まれる)を参照stackoverflow.com/questions/555330/...
エヴァンテラン

3
「Hooray、リンカーエラーなし」でのこの議論にとって意味のあるものになります。
エヴァンテラン

157

C ++のコーディング担当者がThe Wayについて合意し日、子羊はライオンと一緒に横たわり、パレスチナ人はイスラエル人を受け入れ、猫と犬は結婚を許可されます。

現時点では、.hファイルと.cppファイルの分離はほとんど任意であり、コンパイラ最適化の痕跡はずっと昔からあります。私の目には、宣言はヘッダーに属し、定義は実装ファイルに属しています。しかし、それは単なる習慣であり、宗教ではありません。


140
「C ++コーダーがThe Wayに同意した日...」残りのC ++コーダーは1つだけです!
ブライアンエンシンク、2009

9
私は彼らがすでに方法、.hでの宣言、.cppでの定義に同意していると思いました
09

6
私たちはみな盲人であり、C ++は象です。
Roderick Taylor

習慣?では、スコープを定義するために.hを使用するのはどうですか?どの物に置き換えられましたか?
エルナンEche

28

ヘッダー内のコードは、宣言ではなく実際のコードを変更すると、ヘッダーを含むすべてのファイルの再コンパイルを強制するため、一般に悪い考えです。また、ヘッダーを含むすべてのファイルのコードを解析する必要があるため、コンパイルが遅くなります。

ヘッダーファイルにコードを含める理由は、通常、キーワードインラインが適切に機能するため、および他のcppファイルでインスタンス化されているテンプレートを使用するときに必要です。


1
「宣言ではなく実際のコードを変更すると、ヘッダーを含むすべてのファイルが強制的に再コンパイルされます」これが最も純粋な理由だと思います。また、ヘッダーの宣言は.cファイルの実装ほど頻繁には変更されないという事実も伴います。
Ninad 2013

20

同僚に通知しているのは、ほとんどのC ++コードは、最大限の使いやすさを考慮してテンプレート化する必要があるという考えです。また、テンプレート化されている場合は、すべてがヘッダーファイルに含まれている必要があります。これにより、クライアントコードはそれを表示してインスタンス化できます。BoostとSTLにとって十分なものであれば、私たちにとっても十分です。

私はこの見方に同意しませんが、それはそれがどこから来ているのかもしれません。


私はあなたがこれについて正しいと思います。我々はそれを議論するとき、彼はいつもあなたが多かれ少なかれテンプレートの例使用している必要があり、これを行うに。私は「しなければならない」にも同意しませんが、私の代替案はかなり複雑です。
TED

1
@ted-テンプレート化されたコードの場合、ヘッダーに実装を配置する必要があります。'export'キーワードを使用すると、コンパイラはテンプレートの宣言と定義の分離をサポートできますが、エクスポートのサポートはほとんどありません。anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2003/n1426.pdf
マイケルバー

ヘッダは、はい、それは同じヘッダーである必要はありません。以下の不明な回答を参照してください。
TED

それは理にかなっていますが、そのスタイルに出くわしたことはありません。
マイケル・バー、

14

あなたの同僚は賢く、あなたも正しいと思います。

すべてをヘッダーに入れると私が見つけた便利なことは次のとおりです:

  1. ヘッダーとソースを作成して同期する必要はありません。

  2. 構造は単純であり、循環依存によってコーダーが「より良い」構造を作成する必要はありません。

  3. ポータブルで、新しいプロジェクトに簡単に埋め込むことができます。

コンパイル時間の問題には同意しますが、次のことに注意する必要があります。

  1. ソースファイルを変更すると、ヘッダーファイルが変更される可能性が高く、プロジェクト全体が再コンパイルされます。

  2. コンパイル速度は以前よりもはるかに高速です。また、長時間、高頻度でビルドするプロジェクトがある場合は、プロジェクトの設計に欠陥がある可能性があります。タスクを異なるプロジェクトおよびモジュールに分離すると、この問題を回避できます。

最後に、私の個人的な見解で、あなたの同僚をサポートしたいだけです。


3
+1。だれもいないが、ヘッダーのみのプロジェクトでは、コンパイル時間が長いと依存関係が多すぎて、設計が悪いと思われるかもしれないという考えがあった。いい視点ね!しかし、これらの依存関係は、コンパイル時間が実際に短い範囲で削除できるでしょうか?
TobiMcNamobi

@TobiMcNamobi:設計の悪い決定についてより良いフィードバックを得るために「怠けている」というアイデアが大好きです。ただし、ヘッダーのみのコンパイルと個別にコンパイルされた場合、その考えに落ち着くと、単一のコンパイルユニットと膨大なコンパイル時間になります。デザインが実際に素晴らしいときでも。
Jo So

つまり、インターフェイスと実装の分離は、実際には設計の一部です。Cでは、ヘッダーと実装を分離して、カプセル化に関する決定を表現する必要があります。
Jo So

1
現代の言語のようにヘッダーを完全にドロップするだけの欠点はあるのかと思い始めています。
Jo So

12

簡単なメンバー関数をヘッダーファイルに入れて、インライン化できるようにすることがよくあります。しかし、コードの本体全体をそこに配置するために、テンプレートと一貫性を保つために?それは明白なナッツです。

覚えておいてください:愚かな一貫性は、小さな心のホブゴブリンです


ええ、私もそうします。私が使用する一般的なルールは、「コードの1行に収まる場合は、ヘッダーに残します」に沿ったもののようです。
TED

ライブラリがテンプレートクラスの本体をA<B>cppファイルで提供し、ユーザーがA<C>
jww 2017

@jww明示的には述べていませんが、コンパイラーが必要なタイプでインスタンス化できるように、テンプレートクラスはヘッダーで完全に定義する必要があります。これは技術的な要件であり、スタイルの選択ではありません。元の質問の問題は、誰かがテンプレートに適しているか、通常のクラスにも適しているかを誰かが決定したことだと思います。
マークランサム

7

Tuomasが言ったように、ヘッダーは最小限にすべきです。完全にするために、少し拡張します。

私は自分のC++プロジェクトで4種類のファイルを個人的に使用しています。

  • 公衆:
  • 転送ヘッダー:テンプレートなどの場合、このファイルはヘッダーに表示される転送宣言を取得します。
  • ヘッダー:このファイルには、転送ヘッダーがある場合はそれが含まれ、公開したいすべてのものを宣言します(そしてクラスを定義します...)
  • 民間:
  • プライベートヘッダー:このファイルは実装用に予約されたヘッダーであり、ヘッダーが含まれ、ヘルパー関数/構造体(Pimplや述語など)を宣言します。不要な場合はスキップしてください。
  • ソースファイル:プライベートヘッダー(プライベートヘッダーがない場合はヘッダー)を含み、すべてを定義します(非テンプレート...)

さらに、これを別のルールと組み合わせます。転送宣言できるものを定義しないでください。もちろん、私はそこに合理的ですが(どこでもPimplを使用するのはかなり面倒です)。

それは#include私がヘッダーを回避できるときはいつでも、ヘッダーのディレクティブよりもフォワード宣言を好むことを意味します。

最後に、可視性ルールも使用します。シンボルのスコープをできるだけ制限して、シンボルが外側のスコープを汚染しないようにします。

まとめると:

// example_fwd.hpp
// Here necessary to forward declare the template class,
// you don't want people to declare them in case you wish to add
// another template symbol (with a default) later on
class MyClass;
template <class T> class MyClassT;

// example.hpp
#include "project/example_fwd.hpp"

// Those can't really be skipped
#include <string>
#include <vector>

#include "project/pimpl.hpp"

// Those can be forward declared easily
#include "project/foo_fwd.hpp"

namespace project { class Bar; }

namespace project
{
  class MyClass
  {
  public:
    struct Color // Limiting scope of enum
    {
      enum type { Red, Orange, Green };
    };
    typedef Color::type Color_t;

  public:
    MyClass(); // because of pimpl, I need to define the constructor

  private:
    struct Impl;
    pimpl<Impl> mImpl; // I won't describe pimpl here :p
  };

  template <class T> class MyClassT: public MyClass {};
} // namespace project

// example_impl.hpp (not visible to clients)
#include "project/example.hpp"
#include "project/bar.hpp"

template <class T> void check(MyClass<T> const& c) { }

// example.cpp
#include "example_impl.hpp"

// MyClass definition

以下の場合にのみ必要:命の恩人は、ここでは時間の大部分は前方にヘッダは無用であるということであるtypedefまたはtemplateので、実装ヘッダです。)


6

さらに面白くするために、インターフェース.ippを含み、テンプレートの実装(に含まれている.hpp)を含むファイルを追加でき.hppます。

テンプレート化されたコードとは別に(プロジェクトに応じて、これは多数または少数のファイルになる可能性があります)、通常のコードがあり、ここでは宣言と定義を分離することをお勧めします。必要に応じて前方宣言も提供します-これはコンパイル時間に影響を与える可能性があります。


これは、テンプレート定義を使用して実行したことでもあります(ただし、同じ拡張機能を使用したかどうかはわかりません...久しぶりです)。
TED

5

一般に、新しいクラスを作成するときは、すべてのコードをクラスに配置するので、そのために別のファイルを調べる必要はありません。すべてが機能したら、メソッドの本体をcppファイルに分割します、プロトタイプをhppファイルに残します。


4

この新しい方法が本当にThe Wayである場合、プロジェクトで別の方向に進んでいた可能性があります。

ヘッダーで不要なものをすべて避けようとするからです。これには、ヘッダーカスケードの回避が含まれます。ヘッダー内のコードには、おそらく他のヘッダーを含める必要があり、これには別のヘッダーが必要になります。テンプレートを使用せざるを得ない場合は、テンプレートにヘッダーを散らかしすぎないようにします。

また、該当する場合は「不透明なポインタ」パターンを使用します。

これらのプラクティスにより、ほとんどの同業者よりも高速なビルドを行うことができます。そして、はい...コードまたはクラスメンバーを変更しても、大規模な再構築は発生しません。


4

私は自分のヘッダーファイルでこれを個人的に行います:

// class-declaration

// inline-method-declarations

メソッドのコードとクラスを混在させるのは好きではありません。物事をすばやく調べるのは面倒だからです。

すべてのメソッドをヘッダーファイルに含めません。コンパイラーは(通常)仮想メソッドをインライン化できず、(おそらく)ループのない小さなメソッドのみをインライン化します(完全にコンパイラーに依存します)。

クラスでメソッドを実行することは有効です...しかし、読みやすい観点からは、私は好きではありません。ヘッダーにメソッドを配置すると、可能な場合はインライン化されます。


2

私見、彼はテンプレートやメタプログラミングをしている場合にのみメリットがあります。ヘッダーファイルを宣言だけに制限することについては、すでに述べた多くの理由があります。彼らはそれだけです...ヘッダー。コードを含めたい場合は、それをライブラリとしてコンパイルしてリンクします。


2

すべての実装をクラス定義から除外しました。クラス定義からdoxygenコメントを取り出したいのですが。


1
私はそれが遅いことを知っていますが、反対投票者(または同調者)はなぜコメントするのを気にしますか?これは私には理にかなった発言のようです。私たちはDoxygenを使用していますが、問題は確かに発生しました。
TED

2

すべての関数定義をヘッダーファイルに入れるのはまったくばかげていると思います。どうして?ヘッダーファイルがクラスへのPUBLICインターフェイスとして使用されるためです。それは「ブラックボックス」の外側です。

クラスの使用方法を参照するためにクラスを調べる必要がある場合は、ヘッダーファイルを調べる必要があります。ヘッダーファイルには、実行可能な操作のリスト(各関数の使用方法の詳細を説明するためにコメント化)が含まれている必要があり、メンバー変数のリストが含まれている必要があります。個々の関数がどのように実装されているかは含めないでください。これは、不要な情報の大量の読み込みであり、ヘッダーファイルが乱雑になるためです。


1

それは、システムの複雑さと社内の慣習に本当に依存しているのではないでしょうか。

現在、私は信じられないほど複雑なニューラルネットワークシミュレーターに取り組んでいます。

classname.hのクラス定義classnameCode.hの
クラスコードclassname.cppの
実行可能コード

これは、開発者が作成した基本クラスからユーザーが作成したシミュレーションを分割し、状況で最適に機能します。

しかし、たとえばグラフィックスアプリケーションや、ユーザーにコードベースを提供することを目的としない他のアプリケーションでこれを行う人がいるのを見ると、私は驚くでしょう。


1
「クラスコード」と「実行可能コード」の違いは何ですか?
TED

私が言ったように、それはニューラルシミュレータです。ユーザーは、ニューロンなどとして機能する多数のクラスに基づいて構築された実行可能なシミュレーションを作成します。それはシミュレーターに何かをさせます。
Ed James

一般に、ほとんどのプログラムの大部分(全体ではないにしても)について、「実際にはそれ自体では何もできない」とは言えませんか?「メイン」コードはcppに入っているが、他には何もないということですか。
TED

この状況では少し異なります。私たちが作成するコードは基本的にライブラリーであり、ユーザーはこの上にシミュレーションを構築します。これは実際に実行可能です。openGLのように考えてみてください->たくさんの関数とオブジェクトを取得しますが、それらを実行できるcppファイルがないと役に立たないのです。
エドジェームス

0

テンプレートコードはヘッダーのみに含める必要があります。それとは別に、インラインを除くすべての定義は.cppにある必要があります。これに対する最良の議論は、同じルールに従う標準ライブラリの実装でしょう。std lib開発者がこれに関して正しいとは思わないでしょう。


どの stdlibですか?GCCのlibstdc++中でほとんど何も入れないように(AFAICS)らしいsrcのすべて&ほとんどincludeそれはヘッダにある「しなければならない」かどうか、。だから私はこれが正確で役に立つ引用だとは思わない。とにかく、私はstdlibがユーザーコードのモデルの多くであるとは思わない:それらは非常に熟練したプログラマーによって明らかに書かれているが、使用されるべきではなく、読まない:それらは、ほとんどのプログラマーが考える必要がないはずの高い複雑さを抽象化している、_Reserved __namesユーザーとの競合を回避するためにどこでも醜い必要があります。コメントと間隔は、私が助言するものを下回っています。など、狭い意味での模範的なものです。
underscore_d

0

ヘッダーに実行可能コードを書き込むプロセスに参加しない限り、あなたの同僚は正しいと思います。適切なバランスは、.adsファイルがユーザーとその子のためのパッケージの完全に適切なインターフェース定義を提供するGNAT Adaによって示されるパスに従うことだと思います。

ちなみに、テッド、このフォーラムで、数年前に書いたCLIPSライブラリへのAdaバインディングに関する最近の質問をご覧になりましたが、これはもう利用できません(関連するWebページは現在閉じています)。クリップの古いバージョンで作成された場合でも、このバインディングは、Ada 2012プログラム内でCLIPS推論エンジンを使用したい人にとって、良い開始例になる可能性があります。


1
笑。2年後、これは誰かを捕まえる奇妙な方法です。まだコピーがあるかどうかを確認しますが、おそらくないでしょう。私はAdaでコードを実行できるようにAIクラスでそれを行いましたが、誰かが恥知らずにそれを受け取って何かを行うことを期待して、意図的にそのプロジェクトをCC0(本質的に著作権なし)にしました。
TEDの
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.