マクロにしかできないこと 〜C++でマクロを使うべきな場面〜

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++ の標準規格)

*1:C++では連続する文字列 "hoge" "hehe" は連結されて "hogehehe" となることに注意しよう.