次の方法で共有


コンストラクター (C++)

コンストラクターは、そのクラスのインスタンスを初期化する特別なメンバー関数です。 コンストラクターを呼び出すには、クラス名を、中かっこまたは丸かっこで囲んだパラメーターと共に使用します。

class Box {
public:
    Box(int width, int length, int height){
        m_width = width;
        m_length = length;
        m_height = height;
    }

private:
    int m_width;
    int m_length;
    int m_height;

};
class SomeClass{
public:
    static void set_box(const Box& aBox) {}
};
int main(){

    Box box1(1, 2, 3);
    Box box2{ 2, 3, 4 };
    SomeClass::set_box(Box{ 5, 6, 7 });
}

詳細については、「コンストラクターの宣言に関する規則」を参照してください。 初期化の詳細については、「初期化」を参照してください。

コンストラクションの順序

コンストラクターによる処理は次の順序で実行されます。

  1. 基底クラスとメンバーのコンストラクターを宣言の順序で呼び出します。

  2. クラスが仮想基底クラスから派生されている場合は、オブジェクトの仮想基底ポインターを初期化します。

  3. クラスが仮想関数を含むか継承する場合は、オブジェクトの仮想関数ポインターを初期化します。 仮想関数ポインターは、クラスの仮想関数テーブルをポイントし、コードへの仮想関数呼び出しの正しいバインドを可能にします。

  4. その関数本体のコードをすべて実行します。

次の例に、基底クラスとメンバーのコンストラクターが派生クラスのコンストラクターで呼び出される順序を示します。 まず、基底コンストラクターが呼び出され、基底クラスのメンバーがクラス宣言内の出現順に初期化されて、その後に派生コンストラクターが呼び出されます。

#include <iostream>
using namespace std;

class Contained1 {
public:
    Contained1() {
        cout << "Contained1 constructor." << endl;
    }
};

class Contained2 {
public:
    Contained2() {
        cout << "Contained2 constructor." << endl;
    }
};

class Contained3 {
public:
    Contained3() {
        cout << "Contained3 constructor." << endl;
    }
};

class BaseContainer {
public:
    BaseContainer() {
        cout << "BaseContainer constructor." << endl;
    }
private:
    Contained1 c1;
    Contained2 c2;
};

class DerivedContainer : public BaseContainer {
public:
    DerivedContainer() : BaseContainer() {
        cout << "DerivedContainer constructor." << endl;
    }
private:
    Contained3 c3;
};

int main() {
    DerivedContainer dc;
    int x = 3;
}

出力を次に示します。

Contained1 constructor.
Contained2 constructor.
BaseContainer constructor.
Contained3 constructor.
DerivedContainer constructor.

コンストラクターが例外をスローした場合、破棄の順序はコンストラクションの順序と逆になります。

  1. コンストラクター関数本体のコードがアンワインドされます。

  2. 基底クラスとメンバーのオブジェクトが宣言とは逆の順序で破棄されます。

  3. コンストラクターがデリゲート コンストラクターでない場合は、完全に構築されたすべての基底クラスのオブジェクトとメンバーが破棄されます。 ただし、オブジェクト自体が完全に構築されていないため、デストラクターは実行されません。

明示的なコンストラクター

コンストラクターで explicit キーワードを使用すると、コンストラクターのパラメーターが 1 つのみの場合や、1 つのパラメーター以外のすべてのパラメーターに既定値が設定されている場合、暗黙の型変換を防ぐことができます。 詳細については、「コンストラクター (C++)」を参照してください。

既定のコンストラクター

既定のコンストラクター、つまりパラメーターを受け取らないコンストラクターは若干異なる規則に従います。

コンストラクターがクラス内で宣言されていない場合は、コンパイラ時に既定のコンストラクターが生成されます。

class Box {
    int m_width;
    int m_length;
    int m_height;
};

int main(){

    Box box1{};
    Box box2;
}

既定のコンストラクターを呼び出し、丸かっこを使用しようとすると、次の警告が出力されます。

class myclass{};
int main(){
myclass mc();     // warning C4930: prototyped function not called (was a variable definition intended?)
}

これは最も厄介な解析の例です。 例の式は、関数の宣言としても、既定のコンストラクターの呼び出しとしても解釈できるため、さらに、C++ パーサーが宣言を最優先に処理するため、この式は関数宣言として扱われます。 詳細については、「最も厄介な解析」を参照してください。

既定以外のコンストラクターが宣言されている場合は、コンパイル時に既定のコンストラクターは生成されません。

class Box {
public:
    Box(int width, int length, int height){
       m_width = width;
       m_length = length;
       m_height = height;
    }

private:
    int m_width;
    int m_length;
    int m_height;

};

int main(){

    Box box1(1, 2, 3);
    Box box2{ 2, 3, 4 };
    Box box4;     // compiler error C2512: no appropriate default constructor available
}

クラスに既定のコンストラクターがない場合、そのクラスのオブジェクトの配列は、角かっこ構文を使用して構築することはできません。 たとえば、前に示したコード ブロックでは、Boxes の配列は次のように宣言することはできません。

Box boxes[3];    // compiler error C2512: no appropriate default constructor available

ただし、Boxes の配列を初期化する一連の初期化リストを使用することはできます。

Box boxes[3]{ { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };

コピー コンストラクターと移動コンストラクター

コピー コンストラクターは、オブジェクトへの参照を使用して、自身のコピーを作成します。 クラスのコピー コンストラクターを指定しない場合、コンパイル時に既定のコンストラクターが作成されます。これは、コピー操作、移動操作、またはデストラクターが宣言された場合でも同じです。 詳細については、「コンストラクターの宣言に関する規則」を参照してください。

移動コンストラクターでは、オブジェクト間で割り当てられたメモリを転送できます。 詳細については、「方法: 移動コンストラクターを記述する」を参照してください。

明示的に既定化および削除したコンストラクター

コピー コンストラクター、既定のコンストラクター、コピー代入演算子、およびデストラクターの明示的な既定化は可能ですが、移動コンストラクターおよび移動代入演算子の明示的な既定化はサポートされていません。 (このサポートは Visual Studio 2015 現在のものです。) 特殊な関数はすべて明示的に削除できます。 詳細については、「明示的に既定された関数および削除された関数」を参照してください。

派生クラスのコンストラクター

派生クラスのコンストラクターは常に、基底クラスのコンストラクターを呼び出します。それにより、完全に構築された基底クラスに依存して、追加の処理を実行できるようになります。 基底クラスのコンストラクターは派生の順序で呼び出されます。たとえば、ClassA が ClassB から派生され、ClassB が ClassC から派生されている場合、まず ClassC のコンストラクター、次に ClassB のコンストラクター、最後に ClassA のコンストラクターが呼び出されます。

基底クラスに既定のコンストラクターがない場合は、派生クラスのコンストラクターで基底クラスのコンストラクターのパラメーターを指定する必要があります。

class Box {
public:
    Box(int width, int length, int height){
       m_width = width;
       m_length = length;
       m_height = height;
    }

private:
    int m_width;
    int m_length;
    int m_height;
};

class StorageBox : public Box {
public:
    StorageBox(int width, int length, int height, const string label&) : Box(width, length, height){
        m_label = label;
    }
private:
    string m_label;
};

int main(){

    const string aLabel = "aLabel";
    StorageBox sb(1, 2, 3, aLabel);
} 

多重継承を使用するクラスのコンストラクター

クラスが複数の基底クラスから派生されている場合、基底クラスのコンストラクターは、派生クラスの宣言で示されている順序で呼び出されます。

#include <iostream>
using namespace std;

class BaseClass1 {
public:
    BaseClass1() {
        cout << "BaseClass1 constructor." << endl;
    }
};
class BaseClass2 {
public:
    BaseClass2() {
        cout << "BaseClass2 constructor." << endl;
    }
};
class BaseClass3{
public:
    BaseClass3() {
        cout << "BaseClass3 constructor." << endl;
    }
};
class DerivedClass : public BaseClass1, public BaseClass2, public BaseClass3  {
public:
    DerivedClass() {
        cout << "DerivedClass constructor." << endl;
    }
};

int main() {
    DerivedClass dc;
}

予想される出力を次に示します。

BaseClass1 constructor.
BaseClass2 constructor.
BaseClass3 constructor.
DerivedClass constructor.

コンストラクターの仮想関数

コンストラクターの仮想関数の呼び出しは慎重に行うことをお勧めします。 基底クラスのコンストラクターは常に派生クラスのコンストラクターの前に呼び出されるため、基底コンストラクターで呼び出される関数は基底クラスのバージョンであり、派生クラスのバージョンではありません。 次の例では、DerivedClass を構築すると、BaseClassprint_it() 実装が行われてから、DerivedClass コンストラクターにより DerivedClassprint_it() 実装が行われます。

#include <iostream>
using namespace std;

class BaseClass{
public:
    BaseClass(){
        print_it();
    }
    virtual void print_it() {
        cout << "BaseClass print_it" << endl;
    }
};

class DerivedClass : public BaseClass {
public:
    DerivedClass() {
        print_it();
    }
    virtual void print_it(){
        cout << "Derived Class print_it" << endl;
    }
};

int main() {

    DerivedClass dc;
}

出力を次に示します。

BaseClass print_it
Derived Class print_it

コンストラクターと複合クラス

クラス型のメンバーを含むクラスは複合クラスと呼ばれます。 複合クラスのクラス型のメンバーが作成されると、そのコンストラクターはクラス自体のコンストラクターの前に呼び出されます。 含まれるクラスに既定のコンストラクターがないときは、複合クラスのコンストラクターで初期化リストを使用する必要があります。 前に示した StorageBox の例で、m_label メンバー変数の型を新しい Label クラスに変更した場合、両方の基底クラスのコンストラクターを呼び出し、m_label のコンストラクターで StorageBox 変数を初期化する必要があります。

class Label {
public:
    Label(const string& name, const string& address) { m_name = name; m_address = address; }
    string m_name;
    string m_address;
};

class StorageBox : public Box {
public:
    StorageBox(int width, int length, int height, Label label) 
        : Box(width, length, height), m_label(label){}
private:
    Label m_label;
};

int main(){
// passing a named Label
    Label label1{ "some_name", "some_address" };
    StorageBox sb1(1, 2, 3, label1);

    // passing a temporary label
    StorageBox sb2(3, 4, 5, Label{ "another name", "another address" });

    // passing a temporary label as an initializer list
    StorageBox sb3(1, 2, 3, {"myname", "myaddress"});
}

デリゲート コンストラクター

デリゲート コンストラクターは同じクラスの別のコンストラクターを呼び出して、初期化の処理の一部を実行します。 次の例では、派生クラスに 3 つのコンストラクターがあります。2 番目のコンストラクターは最初のコンストラクターに委任し、3 番目のコンストラクターは 2 番目のコンストラクターに委任します。

#include <iostream>
using namespace std;

class ConstructorDestructor {
public:
    ConstructorDestructor() {
        cout << "ConstructorDestructor default constructor." << endl;
    }
    ConstructorDestructor(int int1) {
        cout << "ConstructorDestructor constructor with 1 int." << endl;
    }
    ConstructorDestructor(int int1, int int2) : ConstructorDestructor(int1) {
        cout << "ConstructorDestructor constructor with 2 ints." << endl;
        
        throw exception();
    }
    ConstructorDestructor(int int1, int int2, int int3) : ConstructorDestructor(int1, int2) {
        cout << "ConstructorDestructor constructor with 3 ints." << endl;
    }
    ~ConstructorDestructor() {
        cout << "ConstructorDestructor destructor." << endl;
    }
};

int main() {
    ConstructorDestructor dc(1, 2, 3);
}

出力を次に示します。

ConstructorDestructor constructor with 1 int.
ConstructorDestructor constructor with 2 ints.
ConstructorDestructor constructor with 3 ints.

コンストラクターによって作成されたオブジェクトは、コンストラクターが終了するとすぐに完全に初期化されます。 DerivedContainer(int int1) は成功しますが、DerivedContainer(int int1, int int2) は失敗してデストラクターが呼び出されます。

class ConstructorDestructor {
public:
    ConstructorDestructor() {
        cout << "ConstructorDestructor default constructor." << endl;
    }
    ConstructorDestructor(int int1) {
        cout << "ConstructorDestructor constructor with 1 int." << endl;
    }
    ConstructorDestructor(int int1, int int2) : ConstructorDestructor(int1) {
        cout << "ConstructorDestructor constructor with 2 ints." << endl;
        throw exception();
    }
    ConstructorDestructor(int int1, int int2, int int3) : ConstructorDestructor(int1, int2) {
        cout << "ConstructorDestructor constructor with 3 ints." << endl;
    }

    ~ConstructorDestructor() {
        cout << "ConstructorDestructor destructor." << endl;
    }
};

int main() {

    try {
        ConstructorDestructor cd{ 1, 2, 3 };
    }
    catch (const exception& ex){
    }
}

Output:

ConstructorDestructor constructor with 1 int.
ConstructorDestructor constructor with 2 ints.
ConstructorDestructor destructor.

詳細については、「均一な初期化とコンストラクターのデリゲート」を参照してください。

参照

関連項目

特殊なメンバー関数 (C++)