[ C++で開発 ]

コンストラクタ

C++のコンストラクタについて

本ページはおもに書籍「Effective C++」から

コンストラクタ

デフォルトコンストラクタ

C++では、クラスを定義したときにコンストラクタを定義しなければ、コンパイラが自動的に引数なしのコンストラクタおよびコピーコンストラクタを補完します。

class Person {
    std::string name;
};

と記述したクラスは、デフォルトコンストラクタ、デフォルトデストラクタ、デフォルトコピーコンストラクタ、デフォルト代入演算子関数がコンパイラによって補完され、実質以下のコードと同等になります。

class Person {
    std::string name;
public:
    Person() {}
    ~Person() {}
    Person(const Person& aPerson) { ... }
    Person& operator=(const Person& rhs) { ... }
};

ここで、メンバー変数がstd::string& nameやconst std::stringであると変更が

コピーコンストラクタ

コピーコンストラクタが走る状況

void func1(Person a);
Person func2();

    Person p1;
    Person p2(p1);  // コピーコンストラクタが実行、--- (1)
    Person p3 = p2; // コピーコンストラクタが実行、代入演算子関数は実行されない
    func1(p3);      // コピーコンストラクタが実行、引数への値渡し
    p1 = func2();   // コピーコンストラクタが実行、戻り値の値渡し 

初期化リスト

良くないコンストラクタでのメンバー変数初期化

std::stringをメンバーに持つクラスPersonを考えます。

class Person {
    std::string name;
public:
    Person(const std::string &aName) { name = aName; }
};

これで一見よいように思われます。しかし、このコードでは余分な処理が発生します。

  1. Personクラスのインスタンスを生成しようとする
  2. メンバ変数(string)のインスタンスを空のコンストラクタを実行して生成する
  3. Personクラスのコンストラクタ本体を実行する
  4. 引数で指定したstringインスタンスをメンバ変数nameへコピーする=演算が実行される

2.のときに、stringクラスのコンストラクタが実行されます。続いてコンストラクタ本体の実行において、=演算子を呼び出してstringインスタンスをコピーします。したがって、2.で実行したコンストラクタは無意味です。

メンバー変数初期化は初期化リストで

class Person {
    std::string name;
public:
    Person(const std::string &aName) : name(aName) {}
};
  1. Personクラスのインスタンスを生成しようとする
  2. 初期化リスト中の定義(nameの初期化リスト)を実施する
  3. Personクラスのコンストラクタ本体を実行する

引数なしコンストラクタでも初期化子リストで

class Person {
    std::string name;
public:
    Person() : name() {}
};

基底クラスの初期化も初期化子リストで

class Student : public Person {
    int id;
public:
    Student() : Person(), id(0) {}
};

explicitな変換コンストラクタ

引数が1つのコンストラクタは、思いもよらない変換を行うことがあります。

class Counter {
    int count;
public:
    Count(int init) : count(init) {}
};

と引数にint型を1つ取るコンストラクタを定義したクラスがあると、以下のような局面でCounterインスタンスが生成されます。

    Counter pageHit;
        :
    pageHit = 14;

右辺値のint型の14をCounterに変換しようとします。その際Counter(14)が呼ばれることになります。このとき、コンストラクタにexplicit宣言を追加すると、変換が行われず、コンパイルエラーになります。

class Counter {
    int count;
public:
    explicit Count(int init) : count(init) {}
};

明示的にコンストラクタを呼び出すか、キャストを指定する必要があります。

    Counter pageHit;
        :
    pageHit = Counter(14);
    pageHit = static_cast<Counter>(14);

例外:コピーコンストラクタはexplicit宣言しない

コピーコンストラクタも引数が1つですが、これをexplicit宣言すると、関数の引数・戻り値に値渡しを使用することができなくなります。