移植性の高いプログラムの書き方
namespace編

しのはらのC++実験室トップページ
「移植性の高いプログラムの書き方」トップページ

 namespaceというのは知っていますよね。 名前空間という最近C++に加わった便利な機能のためのキーワードです。 namespaceが使えないコンパイラでも使用できるコードを書きたい時、 最も簡単な方法は「namespaceなんて要らない」といって使用しないことです。 でもnamespaceというのは、 一度その良さを知ってしまうと使わずにはいられなくなる物なのです。
 そう言うわけでnamespaceを積極的に使用しつつ、 namespaceのないコンパイラでもちゃんとコンパイルできるコードに挑戦しました。

マクロの出番です

 まず始めに、 使用しているコンパイラがnamespaceに対応しているかどうかを判定して、 対応していない場合にマクロM__NO_NAMESPACE が定義されているとします。
 そして僕は次のようなコードをヘッダに書きました。
#ifndef M__NO_NAMESPACE
#	define M__BEGIN_NAMESPACE  namespace mylib{
#	define M__END_NAMESPACE    }
#	define M__USING_NAMESPACE  using namespace mylib;
#	define M__NAME_SPEC        ::mylib
#else
#	define M__BEGIN_NAMESPACE
#	define M__END_NAMESPACE
#	define M__USING_NAMESPACE
#	define M__NAME_SPEC
#endif
 どうもここまでは誰もが思い付くことらしいのです。 サークルの先輩にもほとんど同じ事をしている人を発見して、 少しうれしかったです。
 でも最後のM__NAME_SPECマクロだけは苦心したんです。 その辺を説明しようと思います。

簡単な説明

 まず最初の M__BEGIN_NAMESPACEマクロと M__END_NAMESPACEマクロによって、 ライブラリのHやCPPファイルのクラス宣言や定義を囲みます。 そうすることでnamespaceが有効なコンパイラでは、 それらを適切な名前空間に配置できます。

 次にライブラリを使用するユーザーが移植性の高いコードを書きたいときは、 ヘッダをincludeしたあとでM__USING_NAMESPACE マクロを使用します。これはnamespaceに対するusing宣言に置換されます。

 そして4つめのマクロM__NAME_SPEC(上の定義を参照) について説明します。
 このマクロは、ライブラリを収めている名前空間の名前の前にダブルコロン を付けたものに置換されます。 ライブラリにXというクラスがあるとすれば、 M__NAME_SPEC::Xという表記は、 その時のスコープに関係なく同一のクラスXを表わすことになります。 つまり絶対表現です。この様子を示します。
表記namespace無しnamespace有り
M__NAME_SPEC /*無し*/ ::mylib
M__NAME_SPEC::X ::X ::mylib::X
namespaceが使えないコンパイラでも正しい表記になっている、 と言うことに注意しましょう。良くできているでしょう?

 このマクロはもともと自分のライブラリ(mylib) にあるクラスの中からクラスの外の識別子を、 曖昧さ無く参照するために使おうとしたものです。
 例えば、次のような場合に必要になりました。
M__BEGIN_NAMESPACE
class Matrix{
	/* ... */
public:
	friend void inverse(Matrix& ans, const Matrix& m);
	void inverse(){
		M__NAME_SPEC::inverse(*this,Matrix(*this));
		// この上の行に注目
	}
};
M__END_NAMESPACE
メンバ関数inverseからfriend関数inverse を呼び出したいのですが、 同名のメンバ関数がfriend関数を隠してしまうために、 スコープ解決演算子が必要になるわけです。 クラス自体がグローバルである場合は::inverseとすれば良いのですが、 クラス自体がnamespaceに囲まれている場合はmylib::inverse あるいは::mylib::inverseと書かねばなりません。 その区別のために作ったのがM__NAME_SPECマクロなわけです。

それを確立するために苦労しました
〜::(ダブルコロン)演算子〜

 ここまでに説明した方法は、僕が知っている最良の方法です。 実はこれを確立するまでには結構、試行錯誤があったのです。

 最初はM__NAME_SPECマクロには直後の:: (ダブルコロン)を含めていました。 マクロを使用する場所では当然::を書きません。 つまり、
M__NAME_SPEC inverse(/*..... */ );
のように書いていたのです。これでBorland C++やWatcom C/C++では、 全く問題がなかったのです。
 ところがCINTを使ったら問題が起こりました。 このマクロを使用するときは上の例のように、 どうしてもマクロと次の識別子の間に空白が必要になってしまいます。
 これはANSI C++の仕様によれば、何の問題もないことのはずなのですが、 どうもCINTはこの「::の後の空白」を許さないらしいのでした。 (ちなみに現在の最新バージョンではその問題はなくなっているようです。)
 と言うわけで、次のようなやり方に変更したらうまくいきました。
#ifndef M__NO_NAMESPACE
#	define M__NAME_SPEC(x) ::bslib::x
#else
#	define M__NAME_SPEC(x) ::x
#endif
 そうして結局は最もスマートな、このページの最初に示したやり方に、 落ち着いたわけです。
 めでたしめでたし。