C++では, #define で定数を定義するな, const TYPE によるグローバル変数(もしくは適当な名前空間に内包されたグローバル変数)を使え, #define でマクロ関数を定義するな,代わりに inline 関数を使え,みたいなことが言われる.これは確かにその通りだ.
1項 #define ではなく, const と inline を使おう
Scott Meyers (スコット・メイヤーズ): Effective C++ (吉川訳, アスキー出版局, 1998)
に書いてあるように, プリプロセッサよりコンパイラに仕事させるべき だ.でもマクロにしかできないことも多々あるわけで.
case 1: 配列のサイズを取得 †
#define SIZE_OF_ARRAY(array) (sizeof(array)/sizeof((array)[0]))
SIZE_OF_ARRAY(配列) とすれば,配列の要素数に置き換えられる.これは, inline 関数ではできない.関数の引数に配列を使うと,自動的にポインタとして扱われてしまうからだ.
case 2: The # operator (マクロ引数の文字列化) †
例えば変数名を文字列として取得したい場合, inline 関数では逆立ちしてもできない.しかし,マクロの # を使えばできる.
#define print(var) std::cout<<#var"= "<<var<<std::endl
これは, print(x) のように使うと
std::cout<<"x""= "<<x<<std::endl
のように置き換えられるマクロだ*1.マクロ引数 (var) に # をつけると (#var),文字列として置換される.文字列化 # が必要な理由は,
#define print(var) std::cout<<"var= "<<var<<std::endl
とマクロを定義すると, print(x) を
std::cout<<"var= "<<x<<std::endl
のように置き換えるだけだからだ.
case 3: The ## operator (連結) †
例えば x が代入されているマクロ引数 HOGE と _0 をくっつけて x_0 のような識別子を生成した場合に使う.こういう連結による識別子の生成も, inline 関数では絶対できない.
#include <iostream>
#include <cmath>
using namespace std;
#define print(var) std::cout<<#var"= "<<var<<std::endl
// case 2 の print の使用例
int main(int argc, char**argv)
{
const double angle[]= {0.5*M_PI, 0.25*M_PI, M_PI/3.0};
#define defc(_j) c##_j= std::cos(angle[_j])
const double defc(0),defc(1),defc(2);
print(c0);print(c1);print(c2);
#undef defc // ここでしか使わないマクロは undef しておく方がいい
return 0;
}
この例では defc(0)... の部分は
const double c0= std::cos(angle[0]), c1= std::cos(angle[1]), c2= std::cos(angle[2]);
のように置換される.タイプミスなどで c の後の番号と配列のインデクスを異なったものにしてしまうと,発見しにくいバグになるから,結構便利なことがわかる.つまり,似たような変数を生成するときに,ミスを減らせる.
例えば Octave のソース mx-op-defs.h では,キャストなどの演算子を生成するためのマクロを定義して,コードを共通化しているが,ここでも連結 ## の使用例がみられる.
おまけ: その他のプリプロセッサ・ディレクティブ †
#define 以外のプリプロセッサ・ディレクティブについても,適当に紹介しておく.
#include †
#include <iostream>
あまりにも当り前だが,これはプリプロセッサにしか実現できない.
#ifdef/#ifndef ~ #endif によるコードの切替え †
例えば C と C++ の両方から include するヘッダで,
#ifdef __cplusplus
extern "C"
{
#endif
こういうプリプロセッサ・ディレクティブを使うことはよくある.
ライブラリのヘッダを2重に読まないようにするために,
#ifndef hogehoge_h
#define hogehoge_h
...
#endif
みたいなコードを書くことも.
#error †
#ifndef HOGEHOGE
#error ERROR!!!!
#endif
これは HOGEHOGE が #define されていない場合に ERROR!!!! というエラーを吐いてコンパイルを中止する,プリプロセッサ・ディレクティブ.
#pragma †
処理系(コンパイラ)が自由に決めていいプリプロセッサ・ディレクティブ.ソースから警告を抑制する場合などに使われる.
参考文献 †
- ISO/IEC 14882:2003 (E): "16 Preprocessing directives" (C++ の標準規格)