17 に答える
あなたの同僚は間違っています。一般的な方法は、コードを .cpp ファイル (または好きな拡張子) に入れ、宣言をヘッダーに入れることです。
コードをヘッダーに配置することには、ときどきメリットがあります。これにより、コンパイラによるより巧妙なインライン化が可能になります。しかし同時に、コンパイラによってインクルードされるたびにすべてのコードを処理する必要があるため、コンパイル時間が台無しになる可能性があります。
最後に、すべてのコードがヘッダーである場合、循環オブジェクト関係 (場合によっては望ましい) を持つことは煩わしいことがよくあります。
要するに、あなたは正しかった、彼は間違っている。
編集:私はあなたの質問について考えてきました。彼の言うことが真実であるケースが1 つあります。テンプレート。ブーストなどの新しい「モダン」ライブラリの多くは、テンプレートを多用し、多くの場合「ヘッダーのみ」です。ただし、これはテンプレートを扱う場合にのみ行う必要があります。これは、テンプレートを扱うときに行う唯一の方法だからです。
編集:もう少し明確にしたい人もいます。「ヘッダーのみ」のコードを書くことのマイナス面についての考えを次に示します。
調べてみると、ブーストを扱うときにコンパイル時間を短縮する方法を見つけようとしている人がかなり多いことがわかります。例: Boost Asio を使用してコンパイル時間を短縮する方法。これは、ブーストが含まれている単一の 1K ファイルの 14 秒のコンパイルを確認しています。14 秒は「急増」しているようには見えないかもしれませんが、通常よりもはるかに長く、大規模なプロジェクトを扱う場合はすぐに加算される可能性があります。ヘッダーのみのライブラリは、かなり測定可能な方法でコンパイル時間に影響を与えます。ブーストはとても便利なので、私たちはそれを容認しています。
さらに、ヘッダーだけでは実行できないことがたくさんあります (boost でさえ、スレッド、ファイルシステムなどの特定の部分にリンクする必要があるライブラリがあります)。主な例は、複数の定義エラーが発生するため、ヘッダーのみのライブラリに単純なグローバルオブジェクトを含めることはできません (シングルトンである忌まわしき方法に頼らない限り)。注: C++17 のインライン変数により、この特定の例は将来実行可能になります。
最後のポイントとして、boost をヘッダーのみのコードの例として使用すると、非常に詳細な情報が見落とされることがよくあります。
Boost はライブラリであり、ユーザー レベルのコードではありません。だから頻繁には変わらない。ユーザー コードでは、すべてをヘッダーに入れると、小さな変更ごとにプロジェクト全体を再コンパイルする必要があります。これは途方もない時間の無駄です (コンパイルごとに変更されないライブラリには当てはまりません)。ヘッダー/ソース間で物事を分割する場合、さらに良いことに、前方宣言を使用してインクルードを減らすと、1 日全体で合計すると再コンパイルの時間を節約できます。
C++ コーダーがThe Wayに同意する日、子羊はライオンと一緒に横たわり、パレスチナ人はイスラエル人を抱きしめ、猫と犬は結婚できるようになります。
.h ファイルと .cpp ファイルの分離は、この時点ではほとんど恣意的であり、過去のコンパイラ最適化の痕跡です。私の目には、宣言はヘッダーに属し、定義は実装ファイルに属します。しかし、それは単なる習慣であり、宗教ではありません。
宣言ではなく実際のコードを変更すると、ヘッダーを含むすべてのファイルの再コンパイルが強制されるため、ヘッダー内のコードは一般的に悪い考えです。また、ヘッダーを含むすべてのファイルのコードを解析する必要があるため、コンパイルが遅くなります。
ヘッダー ファイルにコードを含める理由は、通常、キーワード inline が適切に機能するために必要であり、他の cpp ファイルでインスタンス化されているテンプレートを使用する場合に必要だからです。
同僚に伝えているのは、ほとんどの C++ コードは最大限の使いやすさを実現するためにテンプレート化する必要があるという考えです。また、テンプレート化されている場合は、クライアント コードがそれを認識してインスタンス化できるように、すべてをヘッダー ファイルに含める必要があります。Boost と STL にとって十分であれば、私たちにとっても十分です。
私はこの観点に同意しませんが、それがどこから来ているのかもしれません。
あなたの同僚は賢いと思いますし、あなたも正しいと思います。
すべてをヘッダーに入れるとわかった便利なことは次のとおりです。
ヘッダーとソースを書き込んで同期する必要はありません。
構造は単純であり、コーダーが「より良い」構造を作成することを強制する循環依存関係はありません。
ポータブルで、新しいプロジェクトに簡単に組み込むことができます。
コンパイル時間の問題には同意しますが、次の点に注意する必要があると思います。
ソース ファイルを変更すると、ヘッダー ファイルが変更され、プロジェクト全体が再コンパイルされる可能性が非常に高くなります。
コンパイル速度は以前よりもはるかに高速です。また、プロジェクトを長時間かつ高頻度でビルドする場合は、プロジェクトの設計に欠陥があることを示している可能性があります。タスクを別のプロジェクトに分割し、モジュールを使用すると、この問題を回避できます。
最後に、私の個人的な見解として、あなたの同僚をサポートしたいと思います。
多くの場合、単純なメンバー関数をヘッダー ファイルに入れ、インライン化できるようにします。しかし、テンプレートと一貫性を保つためだけに、そこにコード全体を配置するには? それは普通のナッツです。
覚えておいてください:愚かな一貫性は、小さな心のホブゴブリンです.
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
orの場合にのみ必要です;)template
.ipp
さらに面白くするために、テンプレートの実装 (に含まれている.hpp
)を含むファイルを追加できます.hpp
が、インターフェイスは含まれています。
テンプレート化されたコード (プロジェクトに応じて、ファイルの大部分または少数)とは別に、通常のコードがあり、ここでは宣言と定義を分離することをお勧めします。必要に応じて前方宣言も提供します - これはコンパイル時間に影響を与える可能性があります。
通常、新しいクラスを作成するときは、すべてのコードをクラスに入れるので、別のファイルを探す必要はありません。すべてが機能したら、メソッドの本体を cpp ファイルに分割します。プロトタイプを hpp ファイルに残します。
私は個人的にヘッダーファイルでこれを行います:
// class-declaration
// inline-method-declarations
メソッドのコードとクラスを混在させるのは好きではありません。すばやく調べるのが面倒だからです。
すべてのメソッドをヘッダー ファイルに入れるわけではありません。コンパイラは (通常) 仮想メソッドをインライン化できず、(おそらく) ループのない小さなメソッドのみをインライン化します (完全にコンパイラに依存します)。
クラスでメソッドを実行することは有効ですが、読みやすさの観点からは好きではありません。メソッドをヘッダーに入れるということは、可能であればインライン化されることを意味します。
この新しい方法が本当にTheWayである場合、プロジェクトで別の方向に進んでいた可能性があります。
ヘッダー内の不要なものをすべて回避しようとしているためです。これには、ヘッダーカスケードの回避が含まれます。ヘッダー内のコードには、おそらく他のヘッダーを含める必要があり、別のヘッダーなどが必要になります。テンプレートを使用せざるを得ない場合は、ヘッダーにテンプレートを散らかしすぎないようにします。
また、該当する場合は「不透明なポインタ」パターンを使用します。
これらのプラクティスにより、ほとんどのピアよりも高速なビルドを実行できます。そしてそうです...コードやクラスメンバーを変更しても、大規模な再構築は発生しません。
すべての実装をクラス定義から外しました。クラス定義から doxygen コメントを除外したいと考えています。
私見、彼はテンプレートやメタプログラミングを行っている場合にのみメリットがあります。ヘッダー ファイルを宣言のみに制限する理由は、すでに述べたとおりです。それらはただの... ヘッダーです。コードを含めたい場合は、それをライブラリとしてコンパイルし、リンクします。
それは、システムの複雑さと社内の慣習に大きく左右されるのではないでしょうか?
現在、私は信じられないほど複雑なニューラル ネットワーク シミュレーターに取り組んでおり、私が使用することが期待されている受け入れられたスタイルは次のとおりです。
classname.h 内のクラス定義 classnameCode.h 内
のクラス コード classname.cpp 内の
実行可能コード
これにより、開発者が作成した基本クラスからユーザーが作成したシミュレーションが分割され、状況に応じて最適に機能します。
しかし、たとえばグラフィックス アプリケーションや、ユーザーにコード ベースを提供することを目的としないその他のアプリケーションでこれを行う人がいることに驚かされます。
ヘッダーに実行可能コードを書き込むプロセスに入らない限り、あなたの同僚は正しいと思います。適切なバランスは、.ads ファイルがユーザーとその子に対してパッケージの完全に適切なインターフェイス定義を提供する GNAT Ada によって示されるパスに従うことだと思います。
ところで Ted さん、このフォーラムで、あなたが数年前に書いた CLIPS ライブラリへの Ada バインディングに関する最近の質問をご覧になりましたか?これはもう利用できません (関連する Web ページは現在閉鎖されています)。古いバージョンの Clips に作成されたとしても、このバインディングは、Ada 2012 プログラム内で CLIPS 推論エンジンを使用することをいとわない人にとって、良い開始例になる可能性があります。