平成27年11月29日
汎用の高速シリアル通信機能(21) C++のハマリ所(1) コンストラクタ(2)
昨日のブログを読み返してみると、どうも結論の表現が弱いと感じます。もう少し注意点に意識が集まるような書き方が必要と思い、そのため、まず昨日の続きから始めます。昨日の記述も一部追加訂正しました。
1.5 コンストラクタの自動呼出しと初期化のまとめ
変数に対するコンストラクタの自動呼出しと初期化の内容について再度、結論をまとめて記述します。
変数に対するコンストラクタの働きは、まずC言語の範疇ではC言語と同じ動作をするように設計されています。
C++の拡張機能が使用される非POD型の構造体ではどんな条件でもコンストラクタが呼び出され変数が初期化される仕様になっているようです。
(ただし、コンパイラが自動生成したコンストラクタでは、非POD型のオブジェクトのみ初期化されます。組み込み型やPOD型のオブジェクトの初期化は明示する必要があるようです。)
この辺りが効率も重視するC言語と安全性を最優先するC++言語の考え方の違いのようです。
具体的には
- 組み込み型の変数ではコンパイラが自動的にデフォルトコンストラクタを呼び出すことは無いので、明示的なコンストラクタ呼び出しが無ければ変数の初期値は不定となる(C言語の仕様に一致させる)。
- POD型の構造体および共用体も同様で、明示的なコンストラクタ呼び出しが泣ければ変数の初期値は不定となる(C言語の仕様に一致させる)。
- C++言語では基本的に変数は(たとえ結果的に無駄であっても)初期化して使用する方が望ましいとの考えから、C言語の仕様が及ばない拡張機能部分(非POD型構造体)では、必ずコンストラクタが呼ばれます。ただし、非POD型構造体メンバーに含まれる組み込み型およびPOD型構造体の初期化には明示的なコンストラクタ呼び出しが必要です。
注意すべきは構造体のメンバーとして非POD型の構造体を持つ構造体は無条件に非POD型になります。つまり、非POD型は子から親へ伝播します。
以上を注意深く調べれば、構造体や変数の初期値が不定になるのか特定の値に初期化されるのかが分かります。
具体的には次のようになります。
typedef struct TEST1_t { //これはPOD型構造体なので、flag, valueともに初期値は不定値となる。
int8_t flag;
long value;
} TEST1_t;
typedef struct TEST2_1_t { //ユーザ定義のコンストラクタがあるので非POD型
int hight; //初期値は呼ばれるコンストラクタによって変化する
int lemg; //どのコンストラクタからも明示的な初期化がされないので不定値になる
TEST2_1_t(int a):hight(a) {} //このコンストラクタが呼ばれた場合にはhightはaに初期化される
TEST2_1_t():hight() {} //このコンストラクタが呼ばれた場合にはhightは0に初期化される
TEST2_1_t():hight(0) {} //このコンストラクタが呼ばれた場合でもhightは0に初期化される
TEST2_1_t(){ //このコンストラクタが呼ばれた場合でもhightは0に初期化される
hight = 0;
//hight{0}; //これでもhightは0に初期化される
}
} TEST2_1_t;
typedef struct TEST2_t { //メンバーに非POD型構造体を含むので非POD型
int8_t flag; //flagの初期値は不定値
long value; //valueの初期値は不定値
TEST2_1_t test2;
} TEST2_t;
これらのルールの複雑さから逃れるために、コンストラクタは個別に記述し基本的には全ての変数を初期化することが推奨されています。
少なくとも初期値が必須となる変数に対しては明示的に初期値を与えるコンストラクタを記述すべきでしょう。
それと複雑な処理はクラスで行い、構造体はC言語の仕様範囲(つまりはPOD型構造体)で使用すると決めれば、構造体でのトラブルの多くを避けることが出来ます。それでもvolatile修飾の問題は残るのですが...
次回は本当に構造体について書いていきます。