インフィニットループ 技術ブログ

2024年04月11日 (木)

著者 : y-murakami

【C++】マクロ入門。ループで任意数の変数を持ったクラスを生成しよう!【ゲーム開発】

 前に一度データ駆動開発とドメイン的な概念の扱いを混ぜてやろうとしてみた時に、キャラクターなどのドメイン的定義からデータ駆動的な基盤でのデータ定義につなげる層で、いくぶんか冗長な記述が表れた経験がありました。また別件ですが、マスターデータの定義からそれに関連するデータ関連のコードを生成する仕組みを見たことがあります。

 C#には、最近はSource Generatorだったか、ボイラープレート的なクラスを宣言するための親密な戦略があります。昔はT4とかいう、よく分からんやつがいた気がします。

 C++はテンプレートによって言語機能内でC#と比べてアグレッシブに静的な事前コード生成による結合を行えますが、クラスの定義そのものを自由度高く行うようなことは出来ません。テンプレートでは型や値の注入は出来ますが、宣言される変数の名前変更等はできません、

 まあ、C++にも何かしら調べればツールか何かがあるのかもしれないんですが、C++のよく分からん情報はそんな調べる気にもなりません(は?)。ですから、簡単な言語機能の範囲でどうにかやってみたいと思います。

 そうです。マクロです!皆やめろ!って言ってきますね。

 現代人として誇るべきかもしれないのですが、私はそもそもマクロを使ったことがないどころか、何ができるのかさえわかっておりせんでした。C++を触り始めて半年程度だという事情はあるものの、無知すぎるやろということで、忌み嫌われる(?)マクロが何者かを知るべく、全く大した話ではないのですが触ってみることにしました。

成果物

 成果物を先に貼ってしまいます。

へ~、クラス名と変数書くだけで、こんなクラス作ってくれるの!?

マクロいいじゃん!で、どうやって作ったの?

ウワワアアーーーーーッ!!!!

 こんな感じになりました。以下にザックリ解説をしていきます。挙動を何となくつかむのが今回の目的でしたから。とはいえ、殆どの記法は調べれば出てくるレベルのものです。今回の肝は、与えた変数の分のループ展開でしょう。

マクロによるループ展開

 関数形式マクロは

#define S(x) f(x)

のような形で表れます。複数の引数を定義したい場合は、以下のようにカンマで区切るようです。

#define S(x,y) x + y

 しかし、残念ながらカンマで区切る複数表現では、今回の目的である任意数の変数宣言のような記述は難しいと思います。僕には出来ませんでした。

 そこで、代わりに以下のようなマクロを使います。

 これは、LOOP_BEGIN()マクロに渡した内容の最後に、_LOOP_ENDをくっつけるだけのマクロです。具体的にどのように使用されるかについて、MEMBER_DEFINITIONマクロがLOOP_BEGINに突っ込まれている箇所があるかと思いますので、そちらをもとに確認していきましょう。

 カッコ内のカンマ区切りによる引数連続のほかの連続表現として、関数マクロは呼び出し時に MACRO(a)(b)(c) というようにかっこを連続して書くことができます。というか、かっこを連続して書くこと自体にはマクロとしての意味があるわけではないのですが、今回のループではこの記法を利用しております。

 マクロの末尾で更に引数なしで関数形式マクロを呼び出すと、自身が利用したかっこを消化(左端のカッコ)したうえでその末尾で呼び出した関数形式マクロを呼び出します。あくまでマクロは文字置き換えですから、置き換えた結果末尾にマクロ文字列が残れば、それがまたマクロとして展開され、残った()らと結合するのは自然な流れですよね。

 かっこが連続で与えられている様子も、成果物として掲示したマクロ呼び出し部分に見られますね。

 マクロの末尾で呼び出したマクロに対して、消化せず残っているカッコが足りない場合は、呼び出しの引数不一致ですから、そこで展開が終了され呼び出そうとしたマクロは単なる文字列として展開されたままになります。ですから、LOOP_BEGINを介してMEMBER_DEFINITIONを呼び出している場合は、その文字列に_LOOP_END付与が行われ、結果として「MEMBER_DEFINITION_LOOP_END」というマクロが末尾に形成されることになります。#define MEMBER_DEFINITION_LOOP_ENDにより、このマクロが無に帰すようにすることで、これをもってループは終了します。

 よって、MEMBER_DEFINITIONでは、任意数の変数定義を行えることが分かるかと思います。

 なお、今回のやり方では型と変数名部分の処理をそれぞれ別のマクロで実施しておりますが、一括で一つのマクロにして、それを再帰的に呼ぶ方が楽に見えるかもしれません。ですが、残念ながら自身を自身からマクロとして呼び出すことはできないようでした。

 また、C++20では__VA_OPT__というものが追加されているようですので、そちらを活用できるような気もします。

最後に

 他のまともな自動コード生成方法を調べたほうが良いのでは?助けてください。

★インフィニットループでは3Dゲーム開発にも取り組んでおります。興味がある方は是非採用情報をご確認ください。★

ブログ記事検索

このブログについて

このブログは、札幌市・仙台市の「株式会社インフィニットループ」が運営する技術ブログです。 お仕事で使えるITネタを社員たちが発信します!