Skip to main content

【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の縛り。
一方母なる 言語 では… ↓

struct PLAYER
{
  int hp;
  const int HP_MAX;
};

// ✅ 「C言語なら」順番を入れ替えても問題なし!!
struct PLAYER playerA = { .HP_MAX = 30, .hp = 10  };  // (‾◡◝)

そのころ次男の 言語 では… ↓

...
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の記事

🍵