web-dev-qa-db-ja.com

ヘッダーファイルのC ++コード

C++での個人的なスタイルでは、インクルードファイルにクラス宣言を、.cppファイルに定義を常に配置する必要があります。これは C++ヘッダーファイル、コード分離 。確かに、このスタイルが好きな理由の一部は、Modula-2とAdaのコーディングに費やしたすべての年に関係していると思われます。どちらも、仕様ファイルとボディファイルで同様のスキームを持っています。

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

私はかつてのように柔軟ではないので、彼と一緒にさらに数人の人を見るまで、彼のこの時流に乗って走り回るのは本当に心配ではありません。それで、このイディオムはどれほど一般的ですか?

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

180
T.E.D.

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

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

最後に、すべてのコードがヘッダーである場合、循環オブジェクトの関係(必要な場合もある)を持つのは面倒です。

要するに、あなたは正しかった、彼は間違っている。

EDIT:私はあなたの質問について考えてきました。彼が言うことが真実であるoneケースがあります。テンプレート。 boostなどの新しい「モダン」ライブラリの多くは、テンプレートを頻繁に使用し、多くの場合「ヘッダーのみ」です。ただし、これはテンプレートを扱うときにのみ行う方法であるため、テンプレートを扱うときにのみ行う必要があります。

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

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

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

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

Boostはライブラリであり、ユーザーレベルのコードではありません。頻繁に変わることはありません。ユーザーコードでは、すべてをヘッダーに入れると、少し変更するたびにプロジェクト全体を再コンパイルする必要があります。これは非常に時間の無駄です(そして、コンパイルごとに変わらないライブラリの場合はそうではありません)。ヘッダー/ソースとさらに良いものの間で物事を分割し、インクルードを減らすために前方宣言を使用すると、1日に追加されたときに再コンパイルの時間を節約できます。

196
Evan Teran

C++コーダーが同意する日The Way、子羊はライオンと寝そべり、パレスチナ人はイスラエル人を抱きしめ、猫と犬は結婚することができます。

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

147

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

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

25
Laserallan

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

私はこの観点に同意しませんが、それがどこから来たのかもしれません。

19
JohnMcG

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

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

  1. ヘッダーとソースを記述および同期する必要はありません。

  2. 構造は単純であり、循環依存関係がないため、コーダーが「より良い」構造を作成することを強制しません。

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

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

  1. ソースファイルの変更は、ヘッダーファイルを変更する可能性が非常に高いため、プロジェクト全体が再コンパイルされます。

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

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

13
XU Bin

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

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

12
Mark Ransom

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

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

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

さらに、これを別のルールと結び付けます。前方宣言できるものを定義しないでください。もちろん私はそこにいるのは理にかなっています(どこでも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
Matthieu M.

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

5
EvilTeach

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

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

5
Anonymous

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

// class-declaration

// inline-method-declarations

すぐに調べるのが苦痛なので、メソッドのコードをクラスに混ぜるのは好きではありません。

すべてのメソッドをヘッダーファイルに入れるわけではありません。コンパイラは(通常)仮想メソッドをインライン化できず、ループのない小さなメソッドのみをインライン化する可能性があります(完全にコンパイラに依存します)。

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

4
TofuBeer

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

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

また、該当する場合は "opaque pointer" -pattern を使用します。

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

4
Virne

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

クラスの使用方法を参照するためにクラスを調べる必要がある場合は、ヘッダーファイルを調べる必要があります。ヘッダーファイルには、できることのリスト(各関数の使用方法の詳細を説明するためにコメントが付けられています)が含まれている必要があり、メンバー変数のリストが含まれている必要があります。それは、個々の機能がどのように実装されるかを含むべきではありません。それは、不必要な情報のボート負荷であり、ヘッダーファイルを混乱させるだけだからです。

2
SeanRamey

すべての実装をクラス定義から除外しました。クラス定義からdoxygenのコメントを削除したいと思います。

2
Jesus Fernandez

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

2
spoulson

それはシステムの複雑さと社内の慣習に本当に依存していないのですか?

現時点では、信じられないほど複雑なニューラルネットワークシミュレータに取り組んでおり、使用が期待される受け入れられているスタイルは次のとおりです。

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

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

ただし、グラフィックアプリケーションなど、ユーザーにコードベースを提供することを目的としていない他のアプリケーションでこれを行う人がいるのには驚かされます。

1
Ed James

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

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

0
Emile

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

0