はじめに
いまさらな感はあるし基本的なことではあるのだが、C/C++ でライブラリを作る時の外部(公開)ヘッダ・内部ヘッダや、複数ライブラリ間の依存関係について整理してみる。
ライブラリの外部(利用者向け)ヘッダと内部ヘッダ
図のように、ライブラリの外部(利用者向け)ヘッダと内部ヘッダの違いを意識して分けたほうがよい。
- 外部ヘッダ: ライブラリ利用者向けの公開ヘッダ
- 内部ヘッダ: 自分のライブラリをコンパイルするときだけに必要なヘッダ
外部ヘッダには利用者が必要な最小限の情報のみが含まれるようにし、実装を隠蔽したほうがよい。
これはなんとなく書いていてもなかなかできず、意識的にやらないと難しい。前方宣言、インターフェイス、pImpl などの設計/実装テクニックを使うことになるだろう。実行効率や設計の複雑度とのトレードオフになることも多いので、ケースバイケースで判断することになる。
ソースツリー上ではフォルダを分けるのが管理しやすい。例えば
-
include/
: 外部ヘッダ -
src/
: ソースファイル(*.c
/*.cc
/*.cpp
) および内部ヘッダ
といったように。
複数ライブラリ間の間接的な依存関係
上の図で、
- モジュールAはライブラリB に依存している
- ライブラリBはライブラリCに依存している
- モジュールAはライブラリCに 間接的に 依存している
という関係である。ここで、3番目の間接的依存関係には2種類ある。
- ライブラリBの公開ヘッダでライブラリCのヘッダを
#include
しているケース- モジュールAはライブラリBのヘッダだけでなく、ライブラリCのヘッダも必要となる
- していないケース
- モジュールXはライブラリCのヘッダは必要としない
- (リンク時にはライブラリCが必要となる)
2 にはいくつかのメリットがある。
- 利用者(A)のコンパイル時に下位ライブラリ(C)のヘッダが不要 (これは上に書いたことそのまま)
- 下位ライブラリ(C)を変更/バージョンアップしてもバイナリ互換が保たれる
- ライブラリBは再コンパイルする必要があるかもしれないが、Aはその必要はない
- 大きなプロジェクトの一部であれば、再コンパイルの時間が節約できる
- 下位ライブラリ(C)のコンパイルオプションを、(直接Cを知らない)モジュールAのコンパイル時に引き継がなくてよい。
-
-DENABLE_XXX=1
のようにコンパイルオプションでヘッダの内容が変わるケースなどは非常に危険だが、ライブラリ内部に隠蔽してしまえば影響範囲が限定できる
-
2 を実現するためには、先に述べたようにメモリや実行効率の低下を伴うテクニックが必要になることも多いので、必要性に応じてどこまでやるか判断するべき。
header-only ライブラリ
C++ の場合ヘッダだけで提供されるライブラリというのもあり、それはそれでメリットデメリットがあるのだが、本項では触れない。
まとめ
C/C++ のライブラリを作るときは、外部(利用者)に公開すべき情報なのか、内部だけで必要な情報なのかを意識して、別のヘッダファイルとして分けよう。