171


45

私のCスタイルでは、クラス宣言をインクルードファイルに、定義を.cppファイルに入れる必要があります。これは、https://stackoverflow.com/a/280048 [Cヘッダーファイルへのロキの答え、コードの分離] ]。 確かに、このスタイルが好きな理由の一部は、Modula-2とAdaのコーディングに費やしたすべての年に関係しています。これらはどちらも仕様ファイルとボディファイルを使った同様の方式です。

私はCよりもずっとCの知識が豊富な同僚を持っています。彼は、可能であれば、すべてのC宣言にヘッダーファイルのその場所に定義を含める必要があると主張しています。 彼はこれが有効な代替スタイルであるとか、もう少し良いスタイルであると言っているのではなく、むしろ誰もが現在Cに使用している新しい広く受け入れられているスタイルです。

私はかつてのようにぎりぎりではありませんので、彼と一緒に数人の人々が現れるまで、彼のこの流行に乗ってスクラッチするのはあまり心配していません。 では、この慣用句は本当に一般的なのでしょうか。

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

17 Answer


188


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

たまにコードをヘッダに入れることにはいくつかの利点があります。これはコンパイラによるより巧妙なインライン化を可能にします。 しかし同時に、すべてのコードがコンパイラによってインクルードされるたびに処理されなければならないため、コンパイル時間が長くなる可能性があります。

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

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

*編集:*私はあなたの質問について考えています。 彼の言うことが真実である*ケース*があります。 テンプレート boostのような最近の多くの新しい "ライブラリ"はテンプレートを多用しており、しばしば "ヘッダのみ"です。ただし、これはテンプレートを扱うときに行う唯一の方法であるため、テンプレートを扱うときにのみ実行する必要があります。

*編集:*もう少し説明をしたいと思う人がいるかもしれません、 "ヘッダーのみ"のコードを書くことの欠点についてのいくつかの考えがここにあります:

検索してみると、boostを扱う際にコンパイル時間を短縮する方法を見つけようとしている人がかなり多くいるでしょう。 例: Boost Asioでコンパイル時間を短縮する方法、これはシングルの14秒コンパイルを見ていますブースト付きの1Kファイル。 14秒は「爆発する」ようには思われないかもしれませんが、それは確かに典型的なものよりずっと長く、そして非常に速く合計することができます。 大規模なプロジェクトを扱うとき。 ヘッダーのみのライブラリーはかなり測定可能な方法でコンパイル時間に影響します。 ブーストはとても便利なので、許容しています。

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

最後のポイントとして、ヘッダーのみのコードの例としてboostを使用する場合、非常に詳細は見逃されることが多い。

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


139


Cコーダーが* The Way *に同意する日、子羊はライオンズと横になり、パレスチナ人はイスラエル人を受け入れ、猫と犬は結婚を許されるでしょう。


23


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

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


19


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

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


12


私はあなたの同僚は頭が良くてあなたも正しいと思います。

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

  1. 書く必要はありません

  2. 構造は明白であり、循環的な依存関係が「より良い」構造を作成するようにコーダーに強制することはありません。

  3. 移植性が高く、新しいプロジェクトへの組み込みが簡単です。

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

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

  2. コンパイル速度は以前よりはるかに高速です。 また、長期間にわたって頻繁にプロジェクトを構築する必要がある場合は、プロジェクトデザインに欠陥がある可能性があります。 タスクを別々のプロジェクトとモジュールに分けることでこの問題を回避できます。

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


12


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

覚えておいてください。http://www.dictionary.com/browse/a-foolish-consistency-is-the-hobgoblin-of-little-minds [愚かな一貫性は、ちょっとしたことの趣味です。]


6


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 MyClassT;

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

// Those can't really be skipped
#include
#include

#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 mImpl; // I won't describe pimpl here :p
  };

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

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

template  void check(MyClass const& c) { }

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

// MyClass definition

ここでの命の恩人は、ほとんどの場合forwardヘッダーが無用であるということです: typedef`または template`の場合にのみ必要で、実装ヘッダーもそうです;)


5


もっと楽しくするために、テンプレート実装(つまり .hpp`に含まれています)を含む .ipp`ファイルを追加することができます。一方、 `.hpp`はインターフェースを含みます。

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


5


一般的に、新しいクラスを書くときは、すべてのコードをそのクラスに入れるので、別のファイルを調べる必要はありません。 すべてがうまくいった後、私はメソッドの本体をcppファイルに分割し、プロトタイプをhppファイルに残します。


4


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

// class-declaration

// inline-method-declarations

メソッドのコードをクラスに混ぜるのは好きではありません。すばやく検索するのは面倒です。

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

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