【C++】マクロについて簡単にまとめてみた

C++言語でプログラミングをするうえでほぼほぼ使うことになるだろうと思う「マクロ」について僕なりにざっくりまとめていこうと思います。

※詳しく解説というよりかは、なんとなくで理解できるようにしているため、言葉足らずなところもあることを理解して読んでいただきたいです。

マクロとは

マクロとは、超簡単に言うと”文字を別の文字に置き換える命令”であり、主に2種類のマクロがあります。

その1:定数マクロ

主な書き方

#define マクロ名 置き換える文字

上のように、「#define」によって、右の内容を左のものとして定義することができます。

使用例

#include <iostream>	// 文字列出力用

#define NAME "ShakeRice" // 一般的にマクロ名は大文字で書く

// マクロ名に日本語も使える(やっちゃダメ!!)
#define 挨拶 "こんにちは!!"

int main()
{
	printf(NAME);
	printf("\n"); // 改行用
	printf(挨拶);
}

// 出力)
// ShakeRice
// こんにちは!!

さて、C++などを少し触ったことがある方には気づくと思います。
書き方、使い方自体は「変数」と大体一緒なんです。

では何が違うのか、といいますと、

アプリケーションが作られる過程にて、開発者が書いたコードをもとに機械語に変換される「コンパイル」時に挙動が異なります。
実際、コンパイル時には上の例のコードは以下のように変換されています。

//省略
int main()
{
	printf("ShakeRice");
	printf("\n"); // 改行用
	printf("こんにちは!!");
}

マクロとして定義したものが、コンパイル時にコード内で使用した箇所へ直接割り当てられます。
主な動作としては大体これくらいです。

別に変数でもよくね?と思う方もいると思いますが、
実際C++では定数マクロよりも同じ「定数」を扱える「const」「constexpr」修飾子を使うことが推奨されています。
できる限りはマクロよりも「const」「constexpr」を使用するほうが良いでしょう。

    定数マクロのメリット

  • 型定義が必要ないため、汎用性がある。
  • 実行時にメモリを消費しない。

    定数マクロのデメリット

  • コンパイル時にコードに直接書き加えられるため、定数名によるデバッグができない
  • 型を定義しないため、バグの温床になる可能性がある。

 

    定数変数のメリット

  • 型を明確に定義する必要があるため、バグの防止につながる。
  • デバッグが容易。

    定数変数のデメリット

  • 定数変数の型の分だけメモリが消費される。

 

ちなみに、「const」と「constexpr」定数の違いを超ざっくりと説明すると、

const : 実行時に値が決定される定数。

constexpr : コンパイル時に値が決定される定数。

となっています。
そのため、現在C++では可能な限り「#define」よりも「constexpr」を使用することが推奨されているようです。

おまけ:

#define NAME "ShakeRice"

// 定義を解除
#undef NAME

1度定義したマクロは「#undef」を使用することで解除することができます。

その2:関数マクロ

#define マクロ名(引数) 処理内容

使用例

 #include <iostream>	// 文字列出力用

// 足し算する関数マクロ
#define ADD(a, b) (a + b)

// 引き算する関数マクロ
#define SUB(a, b) (\
	a - b \
)
// バックスラッシュ(\)で改行できる。
// ただし、バックスラッシュの後には空白やコメントも含め,何も書いてはいけない。

int main()
{
	int addValue = ADD(5, 3);
	printf("%d\n", addValue);

	int subValue = SUB(5, 3);
	printf("%d\n", subValue);
}

// 出力)
// 8
// 2

このように普通の関数のように使うことができます。

実際この程度の処理ならきちんと関数を作ったほうがいいですが、
よく見かけるのが「SAFE_RELEASE」マクロだと思います。

 #define SAFE_RELEASE(p) if(p) { delete p; p = nullptr; }

処理内容は、「ポインタが存在するとき、ポインタを解放してnullptrにする」というものになります。

このメモリ解放関数は何が便利なのかというと、特に一番は「どの型のポインタにも対応できる」ことです。

 #include <iostream>	// 文字列出力用

// メモリ解放マクロ
#define SAFE_RELEASE(p) { delete p; p = nullptr; }

int main()
{
	// int型ポインタにメモリを確保
	int* pIntValue = new int;
	*pIntValue = 10;

	// float型ポインタにメモリを確保
	float* pFloatValue = new float;
	*pFloatValue = 3.14;

	// 値が入っているか確認
	printf("%d\n", *pIntValue);
	printf("%f\n", *pFloatValue);

	// マクロでポインタを解放
	SAFE_RELEASE(pIntValue);
	SAFE_RELEASE(pFloatValue);
	
	if (pIntValue)
	{
		// まだポインタが存在する場合、値を出力
		printf("%d\n", *pIntValue);
	}
	else
	{
		// 解放済みの場合
		printf("pIntValueは解放済み\n");
	}

	if (pFloatValue)
	{
		// まだポインタが存在する場合、値を出力
		printf("%f\n", *pFloatValue);
	}
	else
	{
		// 解放済みの場合
		printf("pFloatValueは解放済み\n");
	}
}

// 出力)
// 10
// 3.140000
// pIntValueは解放済み
// pFloatValueは解放済み

”汎用性がある”のがマクロにおいて特に大きいメリットだと思います。

その3:分岐マクロ

分岐マクロでは、「そのマクロが定義されているか」によって処理内容を変えることができます。

主な書き方

#ifdef BRANCH
// BRANCH が定義されている場合の処理
#endif

#ifndef BRANCH
// BRANCH が定義されていない場合の処理
#endif

これらのものは主に、ビルド時の異なる環境(デバッグ・リリース、WindowsOS・LinuxOSなど)に対応するために使われることが多いです。

#include <iostream> // 文字列出力用

int main()
{
	int value = 3;

#ifdef _DEBUG
	printf("value: %d\n", value); // Debugビルド時のみ値が表示される
#endif // _DEBUG

	// Releaseビルド時は何も表示されない
}

このように、VisualStudioのプロジェクト設定によっても変更することができるので、気になる方は分岐マクロをいろいろ調べてみてもいいでしょう。
「定義済みマクロ」Microsoftドキュメント

さいごに

今回はマクロを触りたての方に向けて、ざっくりとマクロについてまとめてみました。
おおむね最初のほうに使うであろうものを中心に書いていますので、気になった方はいろんなマクロについて調べるのもいいでしょう。

2025/07/22

ブログ記事一覧に戻る