ゲームプログラマのための設計シリーズ:実装詳細編の記事です。
概要
- 指示付き初期化でconstにしつつ、初期化時の変数の取り違えを防ぎやすくなった
- 引数を多くとる関数にも活用
本文
C++17以前の構造体・クラスの初期化
複数のメンバの値を外から初期化したい場合、素朴には以下のような実装が考えられます。
struct Color
{
float r,g,b,a; // (ほんとはこちらにも初期値を入れておいた方がいいです)
};
void func()
{
// パッと見、どの変数を初期化しているのかわかりにくい
const Color color{ 0.0f,0.5f,1.0f,1.0f };
}
しかしこれには、
- どの変数を初期化しているのかわかりづらく、取り違えの危険がある
- Color側の変数の順番を迂闊に入れ替えてしまってもその影響に気付きにくい
といった問題があります。
そこで、
void func()
{
Color color;
color.r = 0.0f;
color.g = 0.5f;
color.b = 1.0f;
color.a = 1.0f;
}
のように書くことで対策していました。
しかし、これはこれで初期化対象をconstにするのをあきらめる必要がありました。(この用途でconst_castを使うのはやりすぎだと思っています)
指示付き初期化
指示付き初期化とは、C++20で追加された変数の初期化方法です。詳しくはリンク先を参照いただくとして、簡単には以下のように書ける機能です。
void func()
{
// .rなどで、メンバ名を指定して初期化できる
const Color color{
.r = 0.0f,
.g = 0.5f,
.b = 1.0f,
.a = 1.0f
};
}
前述の二例のいいとこどりができるようになりました。
デメリットらしいデメリットもほとんど思い当たらないので、積極的に使っていくのをオススメします。
多引数関数にも活用
引数がたくさんある関数は、引数の取り違えが怖いですね。
// 引数が5個を超えると、急に取り違えミスの発生確率が上がるらしい
void ManyArgFunc(int x, int y, int wx, int wy, Color color);
引数を構造体に切り出すと、指示付き初期化が使えて取り違えしにくくできます。
struct ManyArgFuncArg
{
int x;
int y;
int wx;
int wy;
Color color;
};
void ManyArgFunc(const ManyArgFuncArg&);
void func()
{
// どの引数にどの値を与えているか、わかりやすくなった
ManyArgFunc({
.x = 0,
.y = 50,
.wx = 20,
.wy = 10,
// ネストも可能
.color{
.r = 0.0f,
.g = 0.5f,
.b = 1.0f,
.a = 1.0f
}
});
}
余談
指示付き初期化自体の問題ではないのですが、惜しいと思うのが明示的な初期化を強制することはできないということです。
struct Data
{
const int a; // constにしても・・・
const float b;
};
Data d{.b = 0.1f}; // 集成体初期化で記述しなかった変数はゼロ初期化される
明示的な初期化を強制するなら、コンストラクタを使い指示付き初期化はあきらめるしかなさそうです。(せめて、明示的な初期化が必要な変数を別の構造体に分けるくらい)
何かいい方法をご存じであればぜひコメントください。
struct Data
{
Data(int a, float b) : a(a), b(b) {} // これがあるともう指示付き初期化は使えない
const int a;
const float b;
};