C++ の初期化

はじめに

初期化に無頓着な人は C++ の初期化の挙動について知らないということが原因の一つのような気がします
そこでここでは C++ の初期化について説明します

デフォルトコンストラク

デフォルトコンストラクタというのは、引数なしで呼ぶことのできるコンストラクタのことです
ユーザー定義の引数なしで呼ぶことのできるコンストラクタが宣言されているクラスではそれのことです
そうしたコンストラクタのないクラスでは暗黙のコンストラクタのことです

12 Special member functions
12.1 Constructors
5 A default constructor for a class X is a constructor of class X that can be called without an argument.
If there is no userdeclared constructor for class X, a default constructor is implicitly declared.
An implicitly-declared default constructor is an inline public member of its class.

明示的初期化

明示的初期化は明示的に初期値を指定してオブジェクトを定義する初期化です
当たり前ですが、オブジェクトには指定された値が格納されます

int a = 0;
char b[] = "bbb";
CFoo foo( 10 );
CBar* bar = new CBar( 100 );

暗黙的初期化

暗黙的初期化は明示的な初期値が指定されずにオブジェクトが生成される際に適用される初期化です
ゼロ初期化、デフォルト初期化、値初期化のいずれかが適用されます

ゼロ初期化

ゼロ初期化は 0 を格納するための初期化です

標準では次のように言っています

8 Declarators
8.5 Initializers
4 To zero-initialize an object of type T means:
- if T is a scalar type (3.9), the object is set to the value 0 (zero), taken as an integral constant expression, converted to T;94)
- if T is a non-union class type, each non-static data member and each base-class subobject is zero-initialized;
- if T is a union type, the object’s first named data member95) is zero-initialized;
- if T is an array type, each element is zero-initialized;
- if T is a reference type, no initialization is performed.

94) As specified in 4.10, converting an integral constant expression whose value is 0 to a pointer type results in a null pointer value.
95) This member must not be static, by virtue of the requirements in 9.5.

ゼロ初期化とは:

  • スカラー型の場合は 0 を表す値が格納される
  • union でないクラス型の場合は全ての基底クラスと全ての非静的メンバ変数がゼロ初期化される
  • union の場合は名前の付いている最初の非静的メンバ変数がゼロ初期化される
  • 配列の場合は全ての要素がゼロ初期化される
  • 参照型の場合は何もしない

例:

int foo[100] = {}; // 値初期化=全要素を値初期化=要素はスカラなのでゼロ初期化

この初期化が一番基本的な初期化です
C++ の初期化は最終的には「指定された値が格納される」「0 を表す値が格納される」「初期化されない」のどれかになります

デフォルト初期化

デフォルト初期化は初期化子を何も指定しなかった場合の初期化です

標準では次のように言っています

8 Declarators
8.5 Initializers
5 To default-initialize an object of type T means:
- if T is a non-POD class type (clause 9), the default constructor for T is called
  (and the initialization is ill-formed if T has no accessible default constructor);
- if T is an array type, each element is default-initialized;
- otherwise, the object is zero-initialized.

デフォルト初期化とは:

  • 非PODなクラス型ではデフォルトコンストラクタが呼ばれる
    (アクセス可能なデフォルトコンストラクタが無い場合は文法違反とする)
  • 配列の場合は全ての要素がデフォルト初期化される
  • それ以外の場合はゼロ初期化される

例:

class CFoo {
	int foo;
public:
	CFoo():
	foo()	// 値初期化=スカラなのでゼロ初期化
	{
	}
};
CFoo foo; // デフォルト初期化=コンストラクタが呼ばれる

値初期化

値初期化とは空の初期化子が指定された場合の初期化です

標準では次のように言っています

8 Declarators
8.5 Initializers
5 To value-initialize an object of type T means:
- if T is a class type (clause 9) with a user-declared constructor (12.1), then the default constructor for T is called
  (and the initialization is ill-formed if T has no accessible default constructor);
- if T is a non-union class type without a user-declared constructor, then every non-static data member and baseclass component of T is value-initialized;96)
- if T is an array type, then each element is value-initialized;
- otherwise, the object is zero-initialized

96) Value-initialization for such a class object may be implemented by zero-initializing the object and then calling the default constructor.

値初期化とは:

  • 一つでもユーザー定義コンストラクタを持つクラス型の場合にはデフォルトコンストラクタが呼ばれる
    (アクセス可能なデフォルトコンストラクタが無い場合は文法違反とする)
  • ユーザー定義コンストラクタを持たない union でないクラス型の場合には全ての基底クラスと全ての非静的メンバは値初期化される
  • 配列の場合は全ての要素が値初期化される
  • それ以外の場合はゼロ初期化される

例:

struct foo_t {
	int foo;
};
foo_t foo = {}; // 値初期化=全メンバを値初期化=メンバfooはスカラなのでゼロ初期化
int bar[10] = {}; // 値初期化=全要素を値初期化=要素はスカラなのでゼロ初期化

コピー初期化

明らかに不要なコピーは無視される場合もあるので注意

A a = A(...); と書いたとき、標準では一時オブジェクトの生成とコピーが発生するとあります

12 Special member functions
12.6 Initialization
12.6.1 Explicit initialization
1 An object of class type can be initialized with a parenthesized expression-list, where the expression-list is construed as an argument list for a constructor that is called to initialize the object.
  Alternatively, a single assignment-expression can be specified as an initializer using the = form of initialization.
  Either direct-initialization semantics or copy-initialization semantics apply;
  see 8.5. [ Example:
class complex {
	// ...
public :
	complex();
	complex( double );
	complex( double, double );
	// ...
};
complex c = complex(1,2); // construct complex(1,2) using complex(double,double) copy it into c

しかし実際にはこのように明らかに不要なコピーは最適化されてコピーコンストラクタは呼ばれず、complex c(1,2); と書いてあるものとして扱われる場合もあります
同様に関数の戻り値を受け取る際に発生するコピーも省略される場合があります

コピーコンストラクタが呼ばれなくて起きる不具合というのはあまりないでしょうが、念のため気をつけましょう

コピーが不要なクラスではコピーコンストラクタとコピー代入演算子を明示的に空の宣言にしておくと安心です
コピーが発生するような操作をするとコンパイルエラーとして検出できます

class complex {
	.
	.
	.
private:
	complex( const complex& );
	complex& operator=( const complex& );
};

complex c = complex(1,2); // エラー

メンバ初期化

構造体やクラスのメンバの初期化について、標準は次のように言っています

12 Special member functions
12.6 Initialization
12.6.2 Initializing bases and members
4 If a given non-static data member or base class is not named by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer), then
- If the entity is a non-static data member of (possibly cv-qualified) class type (or array thereof) or a base class, and the entity class is a non-POD class, the entity is default-initialized (8.5).
  If the entity is a non-static data member of a const-qualified type, the entity class shall have a user-declared default constructor.
- Otherwise, the entity is not initialized.
  If the entity is of const-qualified type or reference type, or of a (possibly cv-qualified) POD class type (or array thereof) containing (directly or indirectly) a member of a const-qualified type, the program is ill-formed.

After the call to a constructor for class X has completed, if a member of X is neither specified in the constructor's mem-initializers, nor default-initialized, nor value-initialized, nor given a value during execution of the body of the constructor, the member has indeterminate value.
  • コンストラクタのあるものは必ずコンストラクタが呼ばれる
  • 明示的に初期化していない POD は決して初期化されない
  • 初期化されなかった POD は不定値を持つ

初期化ミスの例:

// ユーザー定義コンストラクタを持たない非PODクラス
class CBar {
	std::string foo;
	int bar;
// ユーザー定義のコンストラクタが無い=デフォルトコンストラクタが呼ばれる=fooのコンストラクタは呼ばれる。しかし bar は初期化されない!!
};
class CBaz {
	CBar bar;
public:
	CBaz():
	bar()	// 値初期化=非PODクラスなのでコンストラクタが呼ばれる。きちんと初期化されているように見えるが……
	{
		// しかしここで bar.bar は不定値!!
	}
};
CFoo foo; // デフォルト初期化=コンストラクタが呼ばれる

この例では CBaz の bar はきちんと初期化されているので、CBar のメンバも初期化されそうに見えます
しかし、foo は初期化されますが、bar は初期化されません

CBar は非PODなクラスなので、そのデフォルト初期化ではデフォルトコンストラクタが呼ばれます
非PODなクラスのデフォルトコンストラクタでは基底クラスや非PODなメンバはコンストラクタが呼ばれて適切に初期化されますが、PODなメンバはコンストラクタが無いので初期化されません
一見したところでは不具合であると分かりづらいので、非PODなメンバを持つクラスには必ずコンストラクタを用意するように気をつけてください

コンストラクタの初期化リスト

例1

次のようなコードがあったときにコンパイラはどのようなコードを出力するでしょうか
CBar, CBaz は非PODクラスとします

struct qux_t {
	int qux;
};
class CFoo : public CBar {
	CBaz	m_baz;
	qux_t	m_qux;
	float	m_quux;
	char	m_corge[4];
public:
	CFoo(){}
};
CFoo foo;

まず foo に初期化子が指定されていないので foo 自体はデフォルト初期化されます
デフォルト初期化では CFoo はユーザー定義コンストラクタを持っているのでこれが呼ばれます
CFoo のコンストラクタには初期化リストが何も無いので全ての基底クラスと全ての非PODメンバはデフォルト初期化されます。PODメンバは初期化されません
まず基底クラスは CBar がデフォルト初期化されます
CBar は非PODクラスなのでそのデフォルトコンストラクタが呼ばれます
次に m_baz が非PODクラスなのでそのデフォルトコンストラクタが呼ばれます
他のメンバは POD なので初期化されず不定値のままになります

例2

次のようなコードがあったときにコンパイラはどのようなコードを出力するでしょうか

class CFoo : public CBar {
	CBaz	m_baz;
	qux_t	m_qux;
	float	m_quux;
	char	m_corge[4];
public:
	CFoo():
	m_qux(),
	m_corge()
	{
	}
};
CFoo foo;

まず foo に初期化子が指定されていないので foo 自体はデフォルト初期化されます
デフォルト初期化では CFoo はユーザー定義コンストラクタを持っているのでこれが呼ばれます
CFoo のコンストラクタには初期化リストがあるのでそれに従ってメンバを初期化します
まず初期化リストには書いてありませんが基底クラス CBar がデフォルト初期化されます
CBar は非PODクラスなのでそのデフォルトコンストラクタが呼ばれます
次に初期化リストには書いてありませんが m_baz が非PODクラスなのでそのデフォルトコンストラクタが呼ばれます
次に m_qux が値初期化されます
m_qux は POD なので値初期化はゼロ初期化になります
全てのメンバもゼロ初期化され 0 が格納されます
次に m_corge が値初期化されます
m_corge は POD なので値初期化はゼロ初期化になります
全ての要素もゼロ初期化され 0 が格納されます
m_quux は初期化リストにないので初期化されず不定値のままになります

例3

次のようなコードがあったときにコンパイラはどのようなコードを出力するでしょうか

class CFoo : public CBar {
	CBaz	m_baz;
	qux_t	m_qux;
	float	m_quux;
	char	m_corge[4];
public:
	CFoo():
	CBar(),
	m_baz(),
	m_qux(),
	m_quux(),
	m_corge()
	{
	}
};
CFoo foo;

まず foo に初期化子が指定されていないので foo 自体はデフォルト初期化されます
デフォルト初期化では CFoo はユーザー定義コンストラクタを持っているのでこれが呼ばれます
CFoo のコンストラクタには初期化リストがあるのでそれに従ってメンバを初期化します
まず基底クラス CBar が値初期化されます
CBar は非PODクラスなのでそのデフォルトコンストラクタが呼ばれます
次に m_baz が値初期化されます
m_baz は非PODクラスなのでそのデフォルトコンストラクタが呼ばれます
次に m_qux が値初期化されます
m_qux は POD なので値初期化はゼロ初期化になります
全てのメンバもゼロ初期化され 0 が格納されます
次に m_quux が値初期化されます
m_quux は POD なのでゼロ初期化になり 0 を表す値 0.0f が格納されます
次に m_corge が値初期化されます
m_corge は POD なので値初期化はゼロ初期化になります
全ての要素もゼロ初期化され 0 が格納されます

必ず全ての変数を明示的に初期化すること

このように初期化の挙動を知っていれば何が起きるかはきちんと把握できます
だから初期化を省略していいんだ。ということではありません
このようなことをいちいち人間が把握しながらコードを書かなければいけないという状況はよくありません
余計なことを考えずに常に全てが適切に初期化されている状況にした方がいいに決まっています

コンストラクタでは必ず初期化リストで全てのメンバを明示的に初期化するようにしましょう
ローカル変数も必ず初期化するようにしましょう