ページの末尾へ トップページ > ヘッダファイルの作り方(その 1) > ヘッダファイルの作り方(その 2)
TOPIC 節電しましょう。電力の使用状況グラフ(東電)
C++ のソースコードは、*.cpp ファイルとヘッダファイル *.h に記述されます。これらは、それぞれ、役割分担があるわけですが、C++ のコンパイルのメカニズム的には、ヘッダファイル *.h は、#include によって、*.cpp にインクルードされるということが決まっているだけで、その記述方法のポリシーについては、完全にプログラマーに任されています。このため、ヘッダファイルには、*.cpp に記述できるものは何でも記述できてしまうのですが、だからといって何を書いても良いというわけではなく、正しいポリシーに従って記述しないと、コンパイルエラーの山に悩まされるということになります。
そこで、C++ を正しく楽しむためには、ヘッダの記述方法について、そのポリシーを明確にしておくことは大いに意味があるでしょう。
ヘッダファイルの作り方は、ヘッダの中にテンプレート関連のコードを入れるかどうかで、その作成方法がかなり異なった様相を呈します。ここでは、テンプレートのあるなしを場合に分けて解説します。
ヘッダを使用する目的は、複数の *.cpp ファイルで共通に使用される「何か」を記述することであるといえます。「何か」の代表的なものは、次のようなものです。
・マクロの定義 例、#define MAX_COUNT (10) ・クラスの定義 例、class CManiac { int m_iValue; } ・変数の宣言(定義ではない) 例、extern int g_iCounter;
他に、次のことが出来ます。
・更に他のヘッダをインクルードすること 例、#include <string>
とても重要で、初心者にありがちは間違いは、ヘッダの中にメモリ領域を確保するようなコードを入れてしまうということです。例えば、初心者は、各 *.cpp ファイルで共通に使用しようとする変数をヘッダの中で、次のように定義してしまうことがあります。
<A.h の中> int g_iCounter;
これは、間違いです。なぜ、これがいけないかというと、上記の A.h を X.cpp と Y.cpp の二つの *.cpp ファイルでインクルードして、一緒にコンパイル、リンクすると、リンク時に g_iCounter という変数は重複定義されているというエラーになってしまうからです。この問題が発生する原因は、X.cpp と Y.cpp の中に別個の g_iCounter という名前の変数が出来てしまうためで、コンパイル後に出来上がった X.obj と Y.obj をリンカがリンクしようとするときに、これらが同じ名前であるため、重複定義であるということが判明する事になります。
初心者の、更なる間違いとして、上記のリンクエラーを回避するために、次のように g_iCounter をヘッダの中で static として定義してしまうことがあります。
<A.h の中> static int g_iCounter;
このようにしてしまうと、g_iCounter の名前がエクスポートされなくなるため、リンクエラーは回避されますが、おそらく初心者が意図したようには動作しません。それぞれの *.cpp に固有の変数が確保されるという事情は変わらないため、X.cpp 側の g_iCounter と Y.cpp 側の g_iCounter は別物でまったく無関係なものになってしまいます。
正しい解決方法は、次のようにします。まず、ヘッダの中で変数を extern 宣言します。
<A.h の中> extern int g_iCounter;
そして、どれか一つの *.cpp ファイルの中で、A.h をインクルードし、更に、変数の実体(メモリを確保するコード)を定義します。
<X.cpp の中> #include "A.h" int g_iCounter; // 変数の実体の定義
他の *.cpp ファイルの中では、単に A.h をインクルードします。
<Y.cpp の中> #include "A.h"
これで、各 *.cpp の中で共通の g_iCounter にアクセスできるようになります。
上記をまとめると、次のことが言えます。
・ヘッダの中に、メモリを確保するようなコードを書いてはいけない。 ・メモリを確保するコードは *.cpp の中に書く。 ・変数を共通化するには、変数の extern 宣言をヘッダの中に書く。
上記は、変数についてでしたが、関数についても同様のことが言えます。つまり、ヘッダの中で関数の実体を定義してはいけません。次は間違いの例です。
<A.h の中> void IllegalFunctionDefinition() { int i = 0; } // 関数の実体を定義している。
上記の A.h を X.cpp と Y.cpp の二つの *.cpp ファイルでインクルードして、一緒にコンパイル、リンクすると、リンク時に IllegalFunctionDefinition という関数は重複定義されているというエラーになってしまいます。
また、関数を static として定義してしまうことについても変数と同様の問題が生じます。
<A.h の中> static void IllegalFunctionDefinition() { static int i = 0; }
これも、IllegalFunctionDefinition の名前がエクスポートされなくなるため、リンクエラーは回避され、両 *.cpp で定義される関数の動作は同じなので、問題ないように見えます。しかし、関数の中で、static な変数(上記の例では i)を使っている場合には、それらは、それぞれの *.cpp に固有の変数が確保されるので、変数の場合と同様の問題が生じる可能性が高いといえます。
関数の場合の、正しい解決方法は、次のようにします。まず、ヘッダの中で関数をプロトタイプ宣言します。変数のように extern をつける必要はありません。つけても間違いではありませんが無視されるだけです。プロトタイプ宣言とは、関数の戻り値の型、関数名、関数の引数リストを書き、関数の本体が無いものです。
<A.h の中> void LegalFunctionDefinition(); // 関数のプロトタイプ宣言を記述する。
そして、どれか一つの *.cpp ファイルの中で、A.h をインクルードし、更に、関数の実体を定義します。
<X.cpp の中> #include "A.h" void LegalFunctionDefinition() { int i = 0; } // 関数の実体を定義する。
他の *.cpp ファイルの中では、単に A.h をインクルードします。
<Y.cpp の中> #include "A.h"
これで、各 *.cpp の中で共通の LegalFunctionDefinition にアクセスできるようになります。
上記をまとめると、次のことが言えます。
・ヘッダの中に、関数の実体を定義するコードを書いてはいけない。 ・関数の実体は *.cpp の中に書く。 ・関数を共通化するには、関数のプロトタイプ宣言をヘッダの中に書く。
ヘッダファイルは、自分とは別のヘッダファイルを #include でインクルードできるので、例えば、A.h と B.h が C.h をインクルードし、D.h が A.h と B.h をインクルードするというような事態は、容易に起こりえます。この場合、何も考えないでヘッダを作成すると C.h は D.h に 2 回インクルードされることになりますね。
上記のような、複数回のインクルードは、ヘッダファイルに書かれているもの(例えばクラスの定義など)の重複定義を引き起こすため、避ける必要があります。このために使う定石的な方法が、ワンタイムインクルードとかインクルードガードと呼ばれる方法です。次の例を見てください。
<A.h> の中 #ifndef A_H_20060517_231235 #define A_H_20060517_231235 // ここにヘッダの中身を書く。 #endif // A_H_20060517_231235
上記で A_H_20060517_231235 の部分は、例えば、日付や時刻、ランダムな文字列などを含めて、他のヘッダファイルと重複しないような適当な文字列とします。これで、A.h は 2 度目以降にインクルードされるときには、最初の #ifndef ではじかれて、インクルードしないのと同じことになります。
VC++ の場合には、ヘッダファイルの冒頭に #pragma once と書くことで同様の効果が得られます。
<A.h> の中 #pragma once // ここにヘッダの中身を書く。 // ファイルの終わりには特に何も書く必要は無い。
ここでは、ちょっとおまけ機能で、ワンタイムインクルードのコードを自動生成してみましょう。
下の窓にヘッダファイルの名前を入れて、「ワンタイムインクルードのコード作成」ボタンを押すと、ワンタイムインクルードのコードを別窓に表示します。
当サイトでは、入力された文字列の保存は一切行っていません。しかし、平文がネットワークを通りますので、そういう意味で差し障りの無い文字列を入力してください。また、出力されたコードによって生じる利益、損害などあらゆるものについて、何も保証はいたしませんので予めそのような条件を納得された上でご使用ください。念のため。
ナオ : フム、フム、こういうのをワンタイムインクルードって言うのね。分かったよ、ワトソン君。
店主 : ワトソンじゃーないんだけど。
ナオ : ほぉー、そうかね、ワトソン君。
店主 : だから、ワトソンじゃーないっつーの。