【C++20】PLAYER(10, 30) ←なんの値だっけ
【C++20】構造体と初期化の引数の指示
言語:C++ バージョンは 「20」
難易度と思想
Lv1 → GE1 前期 | 弱
Lv2 → GE1 後期 | 弱
Lv3 → GE2 後期 | 強
Lv1. 構造体の初期化 (基礎)
構造体はお好きですか?
私は大好きです。
// プレイヤーの構造体
struct PLAYER
{
int hp; // 体力!
int hpMax; // 体力の最大値!
};
↑この構造体を使ってプレイヤーをインスタンス化するとき、
こう書くことができると習いました。↓
// プレイヤーのAさん
PLAYER playerA = PLAYER(10, 30);
でもこれ、第1引数と第2引数で hp と hpMax どっちの値どっちだっけ?
となりませんか?
ですからこう書くと分かりやすいと思いますし、先生方もこう書くことがあります。
// プレイヤーのAさん
PLAYER playerA;
playerA.hp = 10;
playerA.hpMax = 30;
Lv2. メンバー変数を定数にしたい…
hpMaxを誤って書き換えてしまわないよう、定数(const)にしよう!
私はそう思ってしまいました。
struct PLAYER
{
int hp; // 体力!
const HP_MAX; // 体力の最大値!定数
};
すると定数への代入はできないため、以下の書き方はできません。
// プレイヤーのAさん
PLAYER playerA;
playerA.hp = 10;
playerA.HP_MAX = 30; //‼️エラー
かと言って PLAYER(10, 30) と書くのは分かりにくいです。
そこで登場 C++20の 指示付き初期化(Designated Initializers)~!!!
PLAYER playerA = PLAYER{ .hp = 10, .HP_MAX = 30 };
これで見やすく初期化できますね!
{ .メンバー変数1 = 値, .メンバー変数2 = 値, ... };
のように、メンバー変数名の頭に「.」(ドット)と、
値の後は「,」(カンマ)を入れる点だけ覚えれば
分かりやすく書くことができます。
以下の細かい制約に注意してください。
struct PLAYER
{
int hp; // 0番目
const int HP_MAX; // 1番目
int level; // 2番目
const int LEVEL_MAX; // 3番目
};
PLAYER{ .hp = 10, .HP_MAX = 30 }; // ✅ すべての変数・定数を書かなくてもOK
PLAYER{ .HP_MAX = 30, .hp = 10 }; // ❌ 順番の入れ替えはNG!(コンパイルエラー)
PLAYER{ .hp = 10, .HP_MAX = 30, .LEVEL_MAX = 30 }; // ✅ 途中のメンバーを飛ばすことは可能
指示付き初期化の応用 [DX11向け]
DirectX 11 を書いているとちょくちょく出てくる なんとか_DESC 系は設定用の構造体です。
設定用の構造体は、最初の初期化後、関数に渡して使うまで変更しません。
学校のゲームエンジンのように、DESC構造体を1つの関数内でたくさんインスタンス化する場合、(関数わけるべき)
誤って意図しない値の上書きをしてしまう危険もあります。
そこで、指示付き初期化を活かして丸ごと定数化(const)しちゃうのがおすすめです!
// 例として深度バッファ用テクスチャ2Dの設定
const D3D11_TEXTURE2D_DESC DESC
{
.Width = static_cast<UINT>(_size.x),
.Height = static_cast<UINT>(_size.y),
.MipLevels = 1,
.ArraySize = 1,
.Format = DXGI_FORMAT_D24_UNORM_S8_UINT, // 深度バッファ用のフォーマット
.SampleDesc =
{
.Count = 1,
.Quality = 0
},
.Usage = D3D11_USAGE_DEFAULT,
.BindFlags = D3D11_BIND_DEPTH_STENCIL,
.CPUAccessFlags = 0,
.MiscFlags = 0,
};
Lv3. 制約が多すぎる!
「指定するメンバー変数の順番が固定」というC++20の縛り。
一方母なるC言語では… ↓
struct PLAYER
{
int hp;
const int HP_MAX;
};
// ✅ 「C言語なら」順番を入れ替えても問題なし!!
struct PLAYER playerA = { .HP_MAX = 30, .hp = 10 }; // (‾◡◝)
そのころ次男のC#では… ↓
...
public class Player
{
public int Hp { get; set; }
public int HpMax { get; init; }
};
...
// ✅ 「C#言語」余裕のよっちゃんイカ!!
Player playerA = new(){ HpMax = 30, Hp = 10 }; // (╹ڡ╹ )
...
悔しいのです。
そんなC++の指示付き初期化の制約は、デザインパターンで解決します。
struct PLAYER
{
int hp;
const int HP_MAX;
};
class PlayerBuilder
{
public:
// コンストラクタを公開
PlayerBuilder() : // メンバーイニシャライザ
hp_{},
hpMax_{}
{}
// デストラクタを公開
~PlayerBuilder() {}
// hp を設定する
// _hp: 設定する体力値
// -> 自身の参照を返す
PlayerBuilder& SetHp(const int _hp)
{
hp_ = _hp;
return *this;
}
// 最大hp を設定する
// _hpMax: 設定する体力の最大値
// -> 自身の参照を返す
PlayerBuilder& SetHpMax(const int _hpMax)
{
hpMax_ = _hpMax;
return *this;
}
// 設定内容からPLAYER構造体のインスタンスを生成
PLAYER Build()
{
return PLAYER{ .hp = hp_, .HP_MAX = hpMax_ };
}
private: // 非公開メンバ変数たち
int hp_; // 体力値
int hpMax_; // 体力の最大値
};
Q. どうやって使うの!!
A. こう書きます。↓
PLAYER playerA = PlayerBuilder().SetHpMax(30).SetHp(10).Build();
これぞ、「Builderパターン」…というらしいです。
o((>ω< ))o「そんなプレイヤー構造体ごときに書いてられるか!」
A.「 た し か に 」
PLAYER構造体に限らずとも、任意の値を安全に設定して構築したいケースは多々あります。
あると思います。たぶん。
そんなときはぜひ参考にしてみてください。
ちなみに私はDiscordボットの開発中にBuilderパターンを知りました。
参考までにEmbedBuilderの記事
🍵