ヘッダーのみのライブラリの利点は何ですか?また、実装を別のファイルに入れるのに反対する方法でそれを書くのはなぜですか?
5 に答える
ヘッダーのみのライブラリの利点:
- ビルド プロセスを簡素化します。ライブラリをビルドする必要はなく、ビルドのリンク ステップでコンパイル済みライブラリを指定する必要もありません。コンパイル済みのライブラリがある場合は、おそらくそのライブラリの複数のバージョンをビルドする必要があります。1 つはデバッグを有効にしてコンパイルし、もう 1 つは最適化を有効にしてコンパイルし、さらにもう 1 つはシンボルを取り除いたものです。マルチプラットフォーム システムの場合は、さらに多くの可能性があります。
ヘッダーのみのライブラリの欠点:
より大きなオブジェクト ファイル。一部のソース ファイルで使用されるライブラリのすべてのインライン メソッドは、そのソース ファイルのコンパイル済みオブジェクト ファイルで、アウト オブ ライン定義のウィーク シンボルも取得します。これにより、コンパイラが遅くなり、リンカも遅くなります。コンパイラはその膨張をすべて生成する必要があり、リンカはそれをフィルタリングする必要があります。
より長いコンパイル。上記の肥大化の問題に加えて、コンパイル済みライブラリよりもヘッダーのみのライブラリの方が本質的にヘッダーが大きいため、コンパイルに時間がかかります。これらの大きなヘッダーは、ライブラリを使用するソース ファイルごとに解析する必要があります。もう 1 つの要因は、ヘッダーのみのライブラリ内のヘッダー ファイルには
#include
、インライン定義で必要なヘッダーと、ライブラリがコンパイル済みライブラリとしてビルドされた場合に必要となるヘッダーが必要であるということです。よりもつれたコンパイル。
#include
ヘッダーのみのライブラリでは余分なs が必要になるため、ヘッダーのみのライブラリではより多くの依存関係が得られます。ライブラリ内のいくつかの重要な機能の実装を変更すると、プロジェクト全体を再コンパイルする必要が生じる可能性があります。コンパイル済みライブラリのソース ファイルでその変更を行うと、その 1 つのライブラリ ソース ファイルを再コンパイルし、コンパイル済みライブラリをその新しい .o ファイルで更新し、アプリケーションを再リンクするだけです。人間には読みにくい。最高のドキュメントがあっても、ライブラリのユーザーはしばしばライブラリのヘッダーを読まなければなりません。ヘッダーのみのライブラリのヘッダーには、インターフェイスの理解を妨げる実装の詳細が含まれています。コンパイルされたライブラリでは、インターフェースと、実装が何をするかについての簡単な解説だけが表示されます。通常は、それだけで十分です。それだけで十分です。ライブラリの使用方法を知るために、実装の詳細を知る必要はありません。
テンプレートを扱う場合など、ヘッダーのみのライブラリが唯一のオプションである場合があります。
ヘッダーのみのライブラリがあるということは、ライブラリが使用される可能性のあるさまざまなプラットフォームについて心配する必要がないことも意味します。実装を分離する場合、通常は実装の詳細を隠し、ライブラリをヘッダーとライブラリ ( lib
、 、dll
または.so
ファイル) の組み合わせとして配布します。これらはもちろん、サポートを提供するすべての異なるオペレーティング システム/バージョン用にコンパイルする必要があります。
実装ファイルを配布することもできますが、それはユーザーにとって余分なステップを意味します-ライブラリを使用する前にコンパイルします.
もちろん、これはケースバイケースで適用されます。たとえば、ヘッダーのみのライブラリが時々増加しますコードサイズ &コンパイル時間。
これが古いスレッドであることは知っていますが、ABI インターフェースや特定のコンパイラの問題について誰も言及していません。だから私はそうすると思った。
これは基本的に、ヘッダー付きのライブラリを作成して人々に配布するか、自分自身を再利用するのではなく、ヘッダーにすべてを含めるという概念に基づいています。ヘッダーとソース ファイルを再利用し、すべてのプロジェクトでこれらを再コンパイルすることを考えている場合、これは実際には当てはまりません。
基本的に、C++ コードをコンパイルして 1 つのコンパイラでライブラリをビルドすると、ユーザーがそのライブラリを別のコンパイラまたは同じコンパイラの別のバージョンで使用しようとすると、バイナリの非互換性が原因でリンカー エラーや奇妙な実行時の動作が発生する可能性があります。
たとえば、コンパイラ ベンダーは、バージョン間で STL の実装を変更することがよくあります。std::vector を受け入れるライブラリに関数がある場合、そのクラスのバイトは、ライブラリがコンパイルされたときに配置された方法で配置されることが期待されます。新しいコンパイラ バージョンで、ベンダーが std::vector の効率を改善した場合、ユーザーのコードは、異なる構造を持つ可能性のある新しいクラスを認識し、その新しい構造をライブラリに渡します。そこからすべてが下り坂になります... これが、ライブラリの境界を越えて STL オブジェクトを渡さないことが推奨される理由です。同じことが C ランタイム (CRT) 型にも当てはまります。
CRT について言えば、通常、ライブラリとユーザーのソース コードは同じ CRT に対してリンクする必要があります。Visual Studio では、マルチスレッド CRT を使用してライブラリをビルドしても、ユーザーがマルチスレッド デバッグ CRT に対してリンクすると、ライブラリが必要なシンボルを見つけられない可能性があるため、リンクの問題が発生します。どの関数だったか思い出せませんが、Visual Studio 2015 の場合、Microsoft は 1 つの CRT 関数をインラインで作成しました。突然、CRT ライブラリではなくヘッダーにあったため、リンク時にそれを見つけることを期待していたライブラリは実行できなくなり、これによりリンク エラーが生成されました。その結果、これらのライブラリは Visual Studio 2015 で再コンパイルする必要がありました。
また、Windows API を使用しているにもかかわらず、ライブラリ ユーザーとは異なる Unicode 設定でビルドすると、リンク エラーや奇妙な動作が発生する可能性があります。これは、Windows API に Unicode または ASCII 文字列を使用する関数と、プロジェクトの Unicode 設定に基づいて正しい型を自動的に使用するマクロ/定義があるためです。ライブラリの境界を越えて間違った型の文字列を渡すと、実行時に問題が発生します。または、そもそもプログラムがリンクしていないことに気付くかもしれません。
これらのことは、他のサードパーティ ライブラリ (固有ベクトルや GSL 行列など) からライブラリの境界を越えてオブジェクト/型を渡す場合にも当てはまります。ライブラリをコンパイルしてからユーザーがコードをコンパイルするまでの間にサードパーティのライブラリがヘッダーを変更すると、問題が発生します。
基本的に安全のために、ライブラリの境界を越えて渡すことができるのは、組み込み型と Plain Old Data (POD) だけです。理想的には、POD は独自のヘッダーで定義され、サードパーティのヘッダーに依存しない構造体にある必要があります。
ヘッダーのみのライブラリを提供すると、すべてのコードが同じコンパイラ設定で同じヘッダーに対してコンパイルされるため、これらの問題の多くは解消されます (サードパーティのライブラリのバージョンとユーザーが使用する API 互換性がある場合)。
ただし、コンパイル時間の増加など、前述のマイナス面もあります。また、ビジネスを運営しているため、ソース コードの実装の詳細をすべてのユーザーに渡したくない場合があります。
主な「利点」は、ソースコードを提供する必要があることです。そのため、マシンやコンパイラで、聞いたことのないエラーレポートが表示されることになります。ライブラリが完全にテンプレートである場合、多くの選択肢はありませんが、選択肢がある場合、通常、ヘッダーのみがエンジニアリング上の選択としては不十分です。(一方、もちろん、ヘッダーは、統合手順を文書化する必要がないことを意味するだけです。)