メンバイニシャライザ


概要

メンバイニシャライザとはコンストラクタ時に使用する初期化方法のことで、
各メンバ変数のコンストラクタをクラスのコンストラクタが始まる直前に実行します。

// 書式
クラス名::コンストラクタ(引数) : メンバ変数(初期値), メンバ変数(初期値)

// 具体例
class CharaBase
{
public:
	CharaBase(void);
	void PrintParam(void);

private:
	int Hp;
	int Mp;
};

CharaBase::CharaBase(void) : Hp(100), Mp(10)
{
}

void CharaBase::PrintParam()
{
	printf("Hp = %d\n", Hp);
	printf("Mp = %d\n", Mp);
}

int main()
{
	CharaBase ch;
	ch.PrintParam();
}

実行結果:
	Hp = 100
	Mp = 10

上のコードではメンバイニシャライザを使用することで
コンストラクタ内で初期化を行う前に初期化を行っています。
メンバイニシャライザを使用するために使用している「」を
イニシャライザ(初期化子)と呼んでいます。

データ型のコンストラクタ

C++が用意しているデータ型(int型やfloat型、char型など)には
コンストラクタが実装されており、これらの型の変数を宣言した際には
各データ型のコンストラクタが実行されています。
この機能を利用することでメンバイニシャライザが実現しています。

// 書式例
データ型 変数名(初期値);

// 具体例
int main()
{
	int value(10);
	float f_value(10.0f);

	printf("value = %d\n", value);
	printf("f_value = %f\n", f_value);
}

実行結果:
	value = 10
	f_value = 10.0

上記の例のようにintやfloatの変数名の後ろに「(初期値)」を記述することで
通常のデータ型でもコンストラクタが実行されて初期化が行われます。

メリット

メンバイニシャライザのメリットは「処理の最適化」と
「constのメンバ変数の初期化」があります。

処理の最適化

クラスのコンストラクタは以下の流れで動作します。

コンストラクタが呼び出される
各メンバ変数のコンストラクタが呼び出される
コンストラクタが実行される
①~③の流れで②でコンストラクタが呼びれだされて、初期化を行い、 ③で更にメンバ変数の初期化を行うならば③の処理は無駄です。 初期化処理の無駄を省いただけですが簡単に実行できるので 取り入れたほうがいいと思います。

constのメンバ変数の初期化

const指定したメンバ変数は本来クラスのメンバ変数の宣言時に
定義を行う必要がありますが、メンバイニシャライザを使用すると
コンストラクタの際に初期化が可能となります。

まずはconstメンバを実装して初期化を行わずエラーになるパターンです。
// constメンバを持ったクラスの例(エラー)
class Test
{
public:
	const int Value;
};
int main()
{
	Test t;
	t.Value;

	return 0;
}

次に宣言時に初期化を行うパターンです。

// constメンバを持ったクラスの例(宣言で初期化)
class Test
{
public:
	const int Value = 10;
};

int main()
{
	Test t;
	printf("Value = %d\n", t.Value);
}

// 実行結果
	Value = 10

最後にメンバイニシャライザを使用して初期化を行うパターンです。

// constメンバを持ったクラスの例(メンバイニシャライザ使用)
class Test
{
public:
	Test(int value) : Value(value)
	{
	}

	const int Value;
};

void main(void)
{
	Test t01(100);
	Test t02(1);

	printf("t01 Value = %d\n", t01.Value);
	printf("t02 Value = %d\n", t02.Value);
}

実行結果
	t01 = 100
	t02 = 1

上のコードからメンバ変数を宣言したときの初期化では各オブジェクト毎で
変数の値を変えることができませんが、メンバイニシャライザを使用した場合は
オブジェクト毎に変数の値を変更出来ていることが分かります。

この二つの初期化方法の使い分けですが、
宣言時初期化はクラス全体でconst変数の値を設定したい場合に使って、
メンバイニシャライザによる初期化は各オブジェクト単位で
const変数の値を設定したい場合に使うようにすれば良いと思います。

初期化の順番

メンバイニシャライザの初期化の順番はクラスで宣言しているメンバ変数を
上から順番に初期化していきます。
イニシャライザに設定している順番ではないので注意をして下さい。

class Test
{
public:
	Test()
	{}
		
private:
	int Hp;
	int Mp;
};

上のコードでは「Hp => Mp」の順番で初期化されます。
次のコードで初期化の順番がメンバイニシャライザの順番ではないことを証明します。

class Test
{
public:
	Test() : Mp(10), Hp(Mp * 100)
	{
	}

	void Print()
	{
		printf("Hp = %d\nMp = %d\n", Hp, Mp);
	}
private:
	int Hp;
	int Mp;
};

int main()
{
	Test t;
	t.Print();
}

実行結果:
	Hp = 不定(コンパイラの初期化しだい)
	Mp = 10

結果はMpは「10」になっているのできちんと初期化されており、
Hpは「100」ではなく別の値が表示されているので初期化は失敗しています。

メンバイニシャライザの初期化の順番ではHp => Mpの順番で初期化されます。
Hpの初期化は「Mp * 100」となっていますが、Mpはまだ初期化されてないので
どのような値が入っているか不明です。
この不明な値に100が掛けられた結果、初期化が失敗しています。

基底クラスコンストラクタとイニシャライザ

継承でも触れていますが、イニシャライザでは基底クラスのコンストラクタの指定も可能です。

// 書式
クラス名::派生クラスコンストラクタ() : 基底クラスコンストラクタ(引数)

// 具体例:
class ObjBase
{
public:
	ObjBase()
	{
		Hp = 5;
		Mp = 5;
	}

	ObjBase(int hp, int mp) : 
			Hp(hp), 
			Mp(mp) 
	{
	}

protected:
	int Hp;
	int Mp;
};

class Player : public ObjBase
{
public:
	Player(int hp, int mp, int money) : 
			ObjBase(hp, mp), // デフォルトコンストラクタの場合は書かなくてもいい
			Money(money)
	{
	}

	Player(int money) :
			ObjBase(),
			Money(money)
	{
	}

	void Print()
	{
		printf("Hp = %d\n", Hp);
		printf("Mp = %d\n", Mp);
		printf("Money = %d\n", Money);
	}

private:
	int Money;
};

int main()
{
	Player pl01(100, 10, 20000);
	Player pl02(10000);
	pl01.Print();
	printf("\n");
	pl02.Print();

	return 0;
}

実行結果:
	Hp = 100
	Mp = 10
	Money = 20000

	Hp = 5
	Mp = 5
	Money = 10000
	

上のコードのようにメンバイニシャライザを使用することで、
基底クラスのデフォルトコンストラクタ以外のコンストラクタを使用可能です。

基底クラスと派生クラスのイニシャライザの順番

基底クラスと派生クラスでイニシャライザを使用した場合、
基底クラスのイニシャライザが先に実行されます。
※イニシャライザの順番を変えても変わりません。

class ObjBase
{
public:
	ObjBase(int hp, int mp) : 
			Hp(hp), 
			Mp(mp) 
	{
	}

protected:
	int Hp;
	int Mp;
};

class Player : public ObjBase
{
public:
	Player(int hp, int mp) : 
			Money(Hp * Mp),
			ObjBase(hp, mp)
	{
	}
			
	void Print(void)
	{
		printf("Hp = %d\n", Hp);
		printf("Mp = %d\n", Mp);
		printf("Money = %d\n", Money);
	}

private:
	int Money;
};

int main()
{
	Player pl(100, 10);
	pl.Print();

	return 0;
}

実行結果:
	Hp = 100
	Mp = 10
	Money = 1000

上のコードではPlayerクラスのメンバイニシャライザの順番を、Moneyが先で、
その次がObjBaseにしていますが、実行結果は問題ない結果になっています。
このことから、基底クラスのコンストラクタが先に実行されていることが分かります。