Hatena::ブログ(Diary)

akihiko’s tech note RSSフィード

2008-12-17

マクロにしかできないこと 〜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++ の標準規格)

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

anonymousanonymous 2017/07/15 20:01 » C++で静的配列の要素数を求めるテンプレート関数 TECHSCORE BLOG https://www.techscore.com/blog/2013/02/08/how-to-calculate-array-length-in-c-and-cpp/
この記事にあるように、 case 1 の配列の要素数の取得については、 template 関数で実現可能であり(そしてこれは暗黙に inline 指定されたものとして扱われます)、マクロを使うべき場面の例として適当ではないかと思われます。
(配列をポインタに勝手に変換しないためには、配列そのものの参照を T(&)[N] で受け取っています。)

投稿したコメントは管理者が承認するまで公開されません。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/aki-yam/20081217/1229498370
Connection: close