スパイス  組み込み制御装置の受注製作

自社標準仕様の製作
平成27年11月28日

 汎用の高速シリアル通信機能(20)  C++のハマリ所(1) コンストラクタ

 先日のRTC検証プログラムのリストには出ていないのですが、ハードウェアタイマのドライバ部分はC++で記述しています。
これは主にC++の学習が目的でC++に慣れることを目的にしてきました。
この中でC++の初心者がハマリやすい点について備忘録として記録しておきます。
ネット上に多数あるC++の入門クラスのHPには、書かれていないか非常に簡単に書かれている程度で,その意味を理解できないことが多い部分のみに注力します。
ただし、私自身が何処まで正確にC++を理解できいるか疑問な部分も多いので、もし疑念があれば他の情報源も含めて調べてください。
この記述内容は鵜呑みに出来るだけの信頼性はありません。疑ってかかる必要があります。

  1.コンストラクタ
 1.1 コンストラクタは全てのオブジェクトに対して存在する。
 コンストラクタの説明は主としてクラスに対して行われるため、クラスにのみコンストラクタが存在するかのように勘違いしそうですが、構造体や変数などにもコンストラクタは存在します。例えばint型の変数a には特に宣言や定義をしなくてもデフォルトコンストラクタとしてa()またはa{}が存在します。正確にはユーザが定義しなければコンパイラが自動的に定義してくれます。
 クラスのコンストラクタを定義するとき下記のようにコンストラクタ名に続いて":"の後に記述するのは変数に対するコンストラクタ呼び出しです。
コンストラクタの呼び出しは先のように丸カッコでも中カッコでも良いようです。
CFunction::CFunction(): a(), b{}, c{} {  //"()"でも"{}"でもコンストラクタを呼び出せる
    ......
} 
ただし、関数の内部から呼び出すなら他の関数プロトタイプと誤認されるので中カッコを使います。
void function()
{
    int a();    //NG: int型を返すaという関数のプロトタイプと認識される。
    int a{};    //OK: aというint型の変数宣言と同時に変数aのデフォルトコンストラクタを呼び出す
}

 1.2 デフォルトコンストラクタの定義
 デフォルトコンストラクタとは、引数を持たないか、または引数なしで呼び出すことが可能なコンストラクタを指します。
下記は共にデフォルトコンストラクタです。コンパイラが自動的に定義したものではなくてもデフォルトコンストラクタとなります。
CFunction::CFunction() {....}    //引数を持たない
//全ての引数にデフォルト値が設定されているため、引数を指定せずに呼び出し出来る
CFunction::CFunction(unsigned int data=0, int arg=1) {....}
 デフォルトコンストラクタはユーザーがその型に対するコンストラクタを何も定義しなければコンパイラが自動的にに用意してくれます。ただし、コンパイラが用意する前提は、ユーザーが一切のコンストラクタを定義しないときに限られます。ある型に対してコンストラクタを一つでも定義するとコンパイラはその型に関して一切のコンストラクタを自動的に用意しません。つまり、引数付きのコンストラクタを一つでも定義すると引数なしのコンストラクタは存在しなくなるため、以下のコードではエラーが発生します。
class CTest {
public:
    CTest(int a, long b){    //この定義によってCTest()というデフォルトコンストラクタの自動生成は行われない。
        ....
    }
    //CTest() {}    //引数付きのコンストラクタを定義するときには、引数なしのデフォルトコンストラクタもユーザー定義する
}

CTest test1(10, 2000);      //OK 引数の型と数が一致するコンストラクタが存在する
CTest test2;                //NG CTest()が定義されていない

 1.3 自動的に生成されるデフォルトコンストラクタの中身
 (引数なしの)デフォルトコンストラクタはユーザが定義しなければ自動的にコンパイラが用意してくれます。ユーザはその処理内容を知っておく必要があります。これは大きく2種類に分かれます。
組み込み型と呼ばれるC++言語に最初から定義されている型(char, int, unsigned longなど)では、基本的に全て0に初期化されます。つまり、
    short, int, longなどの整数型の変数は数値の0に
    float, dobleなどの実数型の変数は数値の0.0に
    char型の変数は'\0'に
    これらの配列は全ての要素が上記の値に初期化されます。

非組み込み型は、非常に複雑になっているようです(従って記述内容に誤りが入り込んでいる可能性が高い)。
 まず構造体では、その構造体がPOD型かそうでないかで処理が分かれ、POD型のコンストラクタでは基本的に構造体の各メンバーを全て0に初期化するようです。構造体のメンバーが構造体の場合も、同じようにそのメンバーを0に初期化していきます。
(H27/11/28追記)
 構造体のメンバーに非POD型の構造体を含む構造体は非POD型になります。つまり、その構造体の子や孫を含めた全てのメンバーがPOD型である必要があります。
(追記ここまで)
非POD型のコンストラクタは「”何もしない”処理が記述される」と書かれたものと「メンバーに含まれる非POD型のコンストラクタを呼び出す」という記述もあり、正直よく分かりません。
(H27/11/28追記)
 基本的にはメンバーに含まれる非POD型のオブジェクトのみ(POD型や組み込み型を除外して)コンストラクタを呼び出すようですが、ここでも複雑な条件があるようです。
もしこの条件で使用するなら、全ての変数に対して必要な初期化を明記することが、唯一の確実な対処方法のようです。
(追記ここまで)

POD型の判定はC++規格の年度によって定義が変わっているらしく、非常に微妙な要素を含んでいるようです。
基本的にはC言語での構造体の範疇に収まる構造体であればPOD型と判断していいようです。C++で拡張された機能を使っていない構造体という認識でいいと思います。
基本的には、構造体は全てPOD型の範疇で使用することにします。上記のような複雑な処理はクラスで行えば良いことです。

 次は共用体です。共用体のデフォルトコンストラクタではメンバー定義の先頭にある変数のコンストラクタを呼び出す処理が記述されます。

 1.4 コンストラクタの自動的な呼び出し
 これもコンストラクタがクラスとセットで説明されるために誤解してしまいがちです。クラスではそのクラスを実体化(インスタンス化)する時には必ずそのクラスのコンストラクタが呼ばれます。プログラム上にコンストラクタの呼び出しを明示してあればそれが実行されますが、明示されない場合にはコンパイラが自動的に(引数なしの)デフォルトコンストラクタを呼び出すコードを追加します。従って、必ずいずれかのコンストラクタが呼び出されることになります。
しかし、クラス以外の変数を実体化する時にも必ずその変数に対するコンストラクタが呼ばれるかというとそうではありません。
 変数が組み込み型の場合、実体化の際にコンパイラはデフォルトコンストラクタを自動的に呼び出すことはしません。従って、組み込み型変数を特定の値に初期化する場合はコンストラクタの明示的な呼び出しが必要です。
void function()
{
    int a;    //int型変数aのコンストラクタは呼ばれないので、初期値は不定
    int a{};  //int型変数aのデフォルトコンストラクタ(引数が無いことに注目)が呼ばれる。この場合変数aは0に初期化される。
}
 非組み込み型では、やはり複雑な処理が行われます。
まず構造体ではPOD型の構造体はデフォルトコンストラクタの自動呼出しは行われません。従って初期化が必要なら明示的な呼び出しを記述します。
非POD型はデフォルトコンストラクタの自動呼出しが行われます。従って、必ずいずれかのコンストラクタが呼び出されます。
 共用体にはデフォルトコンストラクタの自動呼出しはありません。
POD型の構造体と共用体におけるコンストラクタ自動呼出しの仕様はC言語の仕様と一致するように決められているのでしょう。共にコンストラクタを明示的に呼ばない限り、初期値は不定になります。

 次は構造体に関係する内容ですが、日を改めます。

目次へ  前へ  次へ