Aritalab:Lecture/Programming/Cpp/Macro
Wiki Top | Up one level | レポートの書き方 | Arita Laboratory |
|
マクロとは何か
CおよびC++は、コンパイル前にプリプロセッサが文字列置換に基づくプログラム変換をおこないます。以下の構文がプリプロセッサで処理され、一般的にマクロと呼ばれています。
- #include
- #define, #undef
- #if, #ifdef, #ifndef
マクロは文字列置換によってコードの中に展開されるため、使い方によってプログラムの実行速度を落さずに、プログラムを読みやすく、また保守しやすくできます。
includeの使い方
標準ライブラリからファイルを読むときは<>をつけます。
#include <cstdio>
カレントディレクトリにあるインクルードファイルの場合、""をつけます。
#include "list.hh"
if文の使い方
よくみるのは、マシン毎にソースやコンパイルオプションを変更する場合です。
#ifdef WINDOWS ... /* Windows specific code */ #elif defined(UNIX) ... /* Unix specific code */ #else #error "Unsupported OS" #endif
ifdefでなくifを使った場合、defineしたリテラルではなく真偽を返す定数式が書けます。(定数式しか書けないので、ほとんど使いません。) この構文はプログラムソース中でも使えます。以下はプリントデバッグの例です。
#define DEBUG 1 ... /* program source */ #ifdef DEBUG printf("%d\n", value); #endif ... /* program source */
プリプロセッサでは以下の記号が利用できます。
- 文字列の結合 ##
- 引数を文字列として扱う #
- ファイル名、行数、日付、時間 __FILE__、 __LINE__、__DATE__、__TIME__
- C++とCとの区別 _cplusplus (C++のときのみ定義される)
以下のような例はプログラムをわかりづらくするので使用してはいけませんが、参考までに挙げておきます。
#define TO_STRING( s ) #s /* 危険!悪いコード */ ... cout << TO_STRING( Hello World! ) << endl;
これは、cout << "Hello World!" << endl;と同じです。
マクロを使ってはいけない例
定数値の宣言
定数はconstを使って宣言してください。マクロと同様constの値も大文字で定義してください。
const double PI = 3.1415;
列挙型の宣言
明示的な整数型やマクロではなく、enumを使いましょう。定数は大文字にしましょう。
enum { TYPE_CHAR, TYPE_DOUBLE, TYPE_FLOAT, TYPE_INT };
インライン関数の定義
プログラム中で展開したい関数は、マクロではなくinline指定しましょう。
template<class T> inline void swap(T& x, T& y) { T tmp = x; x = y; y = tmp; }
定義する関数を複数のオブジェクト内で利用したい場合は、ヘッダーファイル内に書けばよいだけです(マクロと同じ)。
大小比較
#define MAX(x,y) (x) > (y) ? (x) : (y)と書くのは、化石化しかけた証拠です。 C++プログラマはテンプレートを使ってください。
template<class T> inline bool max(const T& x, const T& y) { return (x > y) ? x : y; }
自分流の型宣言
typedefを使いましょう。
typedef void* GenPtr;
マクロの作法
マクロは、個々のプログラムの為にあるのではなく、プログラミング言語自体を拡張するつもりで利用しましょう。
複数行のマクロはバックスラッシュでつなぐ
マクロの定義が複数行にまたがる場合は、各行の末尾にバックスラッシュを入れます。 またマクロ関数の定義 #define マクロ名 定義本体 でマクロ名と本体を分けるのはスペース記号です。 "関数()"で、関数名と()の間にスペースを入れないようにしましょう。
マクロ全体を () か {} で囲む
コードに展開された時点で、前後の行から影響を受けないように、かならず括弧でくくります。これは数式に関するマクロで特に重要です。括弧はいくらつけても構わないのでマクロの引数も念のために()をつけて展開させましょう。
できる限りテンプレートを使う
テンプレートで用が足せる場合は、できる限りテンプレートを用いましょう。マクロ自体は型チェックをおこなわないのでバグの温床になります。
マクロの例
ここでは数少ない、使える例を紹介してみます。他に、デバッグの仕方を解説したページにもマクロ利用の例があります。
- 配列サイズを返す
#define ARRAY_LENGTH( x ) (sizeof(x)/sizeof(x[0])) : double sampleArray[] = {0.0, 1.1, ... 10.3}; for (int i=0; i < ARRAY_LENGTH( sampleArray ); i++) { ... }
- 自前の簡易イテレータ
C++のSTLにはイテレータという立派な概念がありますが、STLが整備される前は筆者は以下のようなマクロを利用していた時期がありました。
#define LOOP_VAR(y) _loop_var_ ## y template<class T, class TT> inline void* LOOP_SUCC(const T& S, TT& x, void*& p) { x = (TT)p; p = S.succ(x); return x; } #define forall_items(x,S)\ for(void* LOOP_VAR(__LINE__)=(S).head();\ LOOP_SUCC(S,x,LOOP_VAR(__LINE__));)
LOOP_VARで使われている##はプリプロセッサにおける結合演算子で、ここではプログラムの行数に対応した変数名を作成します。こう定義すると、リストや集合を表すクラスでhead(), succ()メソッドを実装していれば(次の要素がない場合はnullを返す)、プログラム中で
forall_items(v, S) { v に関する処理 }
と、UNIX shellに似た記法で(あるいはアルゴリズム本にある擬似コードのように)集合全体への演算を記述できます。マクロの中身を見ればわかるように、_loop_var_というダミー変数を定義してユーザが指定した変数名vに順番に代入しています。