UE4のエンジンを眺めていると、templateとdefine, constexprの全てが使われていて、混乱していたので整理がてらまとめてみる。
間違っているのでツッコミ大歓迎!
全てコンパイル時実行 かつ templateが型安全 なのに何故defineを使うのか???
って思った方の手助けになればと思います。
各種機能概要
template
テンプレート引数を受け取り、引数の情報に従って関数やクラスを生成する機能。
使いこなすことができると悪いことも色々できる。
他2つの機能より抜きんでているイメージ。
// define template.
template <typename T>
void Func(T arg)
{
// do something.
}
// instance template.
Func<int>(5);
Func<double>(1.5);
メリット
- 型に対して安全。
- 特殊化によって特定にテンプレート引数の時に挙動を変えられる。
- type_traitsが使える。(C++11)
デメリット
- 特になし。
他にもデフォルトテンプレート引数とかエイリアステンプレートとかの使い方があるけど、今回は関係ないので割愛する。
define
プリプロセッサ命令
マクロを定義したり、シンボルを定義したりして、コンパイル時の制御をすることができる。
昔ながらの機能ってイメージ
// This is symbol.
# ifndef DEBUG
#define DEBUG
# endif
# undef DEBUG
// This is macro.
# define FUNC(x) (x)*(x)
メリット
- 関数の生成と定数の定義のどちらでも使える。
- シンボルの再定義ができる。
デメリット
- 文字列として扱われるため、型の情報を無視する。
- マクロ展開時に関数の副作用が複数回発生することがある。
constexpr
c++11で追加された定数、関数をコンパイル時に計算する機能。
とりあえず定数定義ならこれ使っておけって印象。
constexpr int KB = 1024;
constexpr int POW(int x) { return x * x; }
constexpr int SIZE = POW(KB);
int hogeArray[SIZE];
メリット
- 記述が容易。
- constexpr関数はコンパイル時でも実行時でも使える。
ここで疑問
Question
templateが型安全なのに、なぜ今だにdefineを使うのか?
Answer
defineでしかできないことがある。
defineにしかできないこと
①シンボル定義
変数名を変えることができる。
##
がキモ
②他プリプロセッサ命令に文字列を与えることができる。
defineはプリプロセッサだからできること。
例としてincludeにマクロ展開したファイル名を与える。
// each macro needs "_INNER" macro, because it expands args macros at first.
# define TO_STR(x) TO_STR_INNER(x)
# define TO_STR_INNER(x) #x
# define SAMPLE_FILE(suffix, file) SAMPLE_FILE_INNER(suffix, file)
# define SAMPLE_FILE_INNER(suffix, file) suffix##file
# include TO_STR(SAMPLE_FILE(sample, Define.h)) // #include "sampleDefine.h"
結論
基本的にはtemplateやconstexprが型安全なのでガンガン使ってよさそう。
定数定義やマクロくらいの関数といった簡単なこと→constexpr
型分岐やコンテナ実装等のちょっとリッチなことをする→template
defineでしかできない→define