[C++] クラスをヘッダファイルで定義するときのかゆい所に手が届かない感じ

■ キーワード

コードの分割管理実行ファイルの分割コンパイルに関する齟齬について

■ 関数プロトタイプのヘッダ

C++プログラミングでは、ヘッダファイルはなくてはならないものです。

ヘッダには主に型の定義や、関数の宣言を書き、プリプロセッサ命令で他のソースファイルに読み込むことができます。

 Cでは関数を呼び出すためには、その位置より前に定義、または宣言しておく必要があります。これは、初期のCコンパイラが1パスだったことに由来するものなのでしょうか(同時期の他の言語、例えばPL/Iなどでは宣言順序は必ずしも重要ではありません)。

そこで、下位の関数から順番に定義するか、予めプロトタイプ宣言をする必要が出てきます。自然にファイル上部に関数プロトタイプ宣言が集まり、そのあとに関数定義が続くわけです。

 分割コンパイルでも、関数プロトタイプ宣言があれば、その関数を使うことができます。

型が宣言されているので呼出しコードが書け、実際の関数アドレスだけ後でリンカが追記すればよいわけです。すると、複数のファイルで利用される関数は、その関数プロトタイプ宣言を共有すればよいことになります。

プロトタイプ宣言を1つのファイルにまとめ、ソースコードから読み込めばよいのです。「ヘッダ」は印刷においては共通の見出しのことをいいます。これが分割コンパイルにおけるヘッダファイルの意味です(余談ですが、JAVAでは、ソースからヘッダ情報であるインターフェースを機械的に抽出しているので、ヘッダファイルを作る必要はありません)。

■ インターフェースとしてのヘッダ

この共通して利用する関数プロトタイプをまとめたもの、という性質から、ヘッダファイルは関数群の説明書の役割を果たすことになります。
それゆえ、副作用としてインターフェースと実装が分離されるということになります。

ただテキストを読み込むという単純なプリプロセッサ命令から、このような隠蔽と分割統治の仕組みを実現している点がCの凄いところです。

■ 非公開メソッドとヘッダ

さて、時代がC++になると、面倒がでてきます。
それは、クラスの定義です。

複数のソースで使われるクラスの定義は、ヘッダファイルで完結していなければならない点です。

これは、インスタンスのために何バイトのメモリを確保するのか、どういう順序でインスタンス変数が並んでいるのかなどという情報がコンパイル時に必要だからです。

すると、クラスにインスタンス変数やメソッドを追加するたびに、ヘッダを修正しなければならなくなります。

例えば、プライベートメソッドもクラス定義で宣言しなければならないため、リファクタリングでメソッドを分割する、などということが厄介になります。

■ まとめ:コンパイラにはprivateな情報も必要

関数宣言とクラス定義の性質の違いが、ヘッダの管理を難しくしているように思います。

モジュール単位で開発する場合は、関数名と呼出し形式を公開し、内部手続きを分割コンパイルをします。したがってヘッダの管理自体は容易でした。

しかし、オブジェクト指向開発では、メソッド名だけでなく、インスタンス変数の並びや仮想関数テーブルなど、さまざまな情報がコンパイル時に(つまり、リンクする前に)必要になります。

例えば、実装の見直しによって非公開インスタンス変数を変更すると、ヘッダを変更する必要があり、インターフェースに変更がなくとも、利用しているソースを再コンパイルしなければならなくなります。


 ヘッダファイルにprivateとして記述されることは、実装上のものなので公開したくないが、まさに実装の都合上、コンパイラが必要とする情報なのですね。

個人的には、JAVAのべた書きよりも、C++のヘッダファイルの方が必要な情報が探しやすくて好きなのですが、クラス定義をしているといろいろ一貫性がなくて、なんだかなあ。