ヘッダファイルの書き方 - その2

ライブラリのヘッダファイルを書く際には、名前空間の汚染に注意しなければなりません。ここでいう「名前空間」とは、C++の namespace のことではなく、もう少し広い意味で使っています。具体的には、名前空間有効範囲を含む有効範囲、マクロの影響範囲、および外部結合を持つ識別子の全体を指しています。

クライアントコードや他のライブラリから身を守るため、そして、クライアントコードや他のライブラリを破壊するのを防ぐためにも、名前空間の汚染に関して理解しておく必要があります。そうでなければ、ライブラリの外部に存在するコードによって引き起こされる、不可解な現象に悩まなければなりません。

ライブラリ内部で使用する識別子

ライブラリの実装上の都合により、ヘッダファイルの中でいくつかの識別子を導入しなければならないことがよくあります。例えば、クラスの非公開メンバ、関数宣言の仮引数、インライン関数や関数テンプレートの定義で用いる局所変数などです。何も考えずにこれらの識別子を命名してしまうと、クライアントコードや他のライブラリによってコードを破壊されるおそれがあります。

例えば、インライン関数の中で count という局所変数を使用していたとしましょう。ところが、アプリケーション側で count というマクロが定義されると、インライン関数のコードが破壊されてしまいます。C++ではマクロは極力使うべきではありませんが、それは行儀作法の問題であって強制力はありません。

逆に、ライブラリのヘッダファイルで count というマクロを定義したとしましょう。今度は、アプリケーション側の関数の中で count という局所変数を使用していた場合、そのコードを破壊してしまうことになります。問題になるのはマクロだけではありません。ライブラリのヘッダファイルで、大域的名前空間に何らかの識別子を導入した場合、アプリケーションや(主にC言語で記述された)他のライブラリの識別子と簡単に衝突してしまいます。

こうした問題を回避するには、ライブラリ内部で使用する識別子には、決まった接頭辞を付けるなどの工夫が必要です。そして、その接頭辞から始まる識別子をライブラリの予約済み識別子として定義する必要があります。また、マクロ以外は、必ず(大域的ではない)何らかの名前空間に入れるべきです。

例えば、foo という名のライブラリを開発する場合、FOO_ で始まる識別子を予約済みにします。そして、

// foo.hpp
#ifndef FOO_HPP
#define FOO_HPP
 
namespace foo {
 
inline int func(int FOO_arg)
{
  int FOO_result;
  FOO_result = FOO_arg + 1;
  return FOO_result;
}
 
}
 
#endif // FPP_HPP

のようにするのが最も安全なのです。

現実的な妥協点

先ほど取り上げた方法が最も安全なのは確かです。しかし、接頭辞の付加を徹底させようとすると、コーディングは非常に面倒ですし、可読性も低下します。そこで、接頭辞を付加するのはマクロだけに留め、他の内部的な識別子には、末尾に下線(アンダースコア)を付加するだけにすることがよくあります。

先ほどの例を書き直すと、

// foo.hpp
#ifndef FOO_HPP
#define FOO_HPP
 
namespace foo {
 
inline int func(int arg_)
{
  int result_;
  result_ = arg_ + 1;
  return result_;
}
 
}
 
#endif // FPP_HPP

のようになります。これでかなり書きやすく、また読みやすくなりました。ライブラリによっては、末尾に下線を付加するのは、クラスの非公開メンバだけにしていることもあるようです。

いずれにせよ、外部コードでマクロを濫用されないことが前提になっています。先ほど、マクロの使用制限に強制力がないことを書きましたが、できるだけ、ライブラリの仕様として制限を課す方がよいのかもしれません。

この記事のトラックバックURL:

http://www.kijineko.co.jp/trackback/365