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

クラス メンバーの初期化方法をカスタマイズしたり、クラスのオブジェクトの作成時に関数を呼び出したりするには、コンストラクター を定義 します。 コンストラクターにはクラスと同じ名前があり、戻り値はありません。 さまざまな方法で初期化をカスタマイズするために、必要な数のオーバーロードされたコンストラクターを定義できます。 通常、コンストラクターはパブリック アクセシビリティを持ち、クラス定義または継承階層外のコードでクラスのオブジェクトを作成できます。 ただし、 または としてコンストラクターを宣言 protected することもできます private

コンストラクターは、必要に応じてメンバー init リストを受け取る場合があります。 これは、コンストラクター本体で値を割り当てるよりも、クラス メンバーを初期化するより効率的な方法です。 次の例は、3 つのオーバーロード Box されたコンストラクターを持つクラスを示しています。 最後の 2 つの use メンバー init リスト:

class Box {
public:
    // Default constructor
    Box() {}

    // Initialize a Box with equal dimensions (i.e. a cube)
    explicit Box(int i) : m_width(i), m_length(i), m_height(i) // member init list
    {}

    // Initialize a Box with custom dimensions
    Box(int width, int length, int height)
        : m_width(width), m_length(length), m_height(height)
    {}

    int Volume() { return m_width * m_length * m_height; }

private:
    // Will have value of 0 when default constructor is called.
    // If we didn't zero-init here, default constructor would
    // leave them uninitialized with garbage values.
    int m_width{ 0 };
    int m_length{ 0 };
    int m_height{ 0 };
};

クラスのインスタンスを宣言すると、コンパイラはオーバーロード解決の規則に基づいて、呼び出すコンストラクターを選択します。

int main()
{
    Box b; // Calls Box()

    // Using uniform initialization (preferred):
    Box b2 {5}; // Calls Box(int)
    Box b3 {5, 8, 12}; // Calls Box(int, int, int)

    // Using function-style notation:
    Box b4(2, 4, 6); // Calls Box(int, int, int)
}
  • コンストラクターは、、明示的、または inlineinlinefriendfriend
  • コンストラクターは、、または として宣言されているオブジェクト const を初期化 volatile できます const volatile 。 オブジェクトは、 const コンストラクターが完了した後になります。
  • 実装ファイルでコンストラクターを定義するには、他のメンバー関数 と同様に修飾名を付け、 を指定します Box::Box(){...}

メンバー初期化子リスト

コンストラクターには、必要に応じて、コンストラクター本体を実行する前にクラス メンバーを初期化するメンバー初期化子リストを指定できます。 (メンバー初期化子リストは、std::initializer_list T 型の初期化子リストとは異なinitializer_list 注意 してください)。

メンバー初期化子リストは、メンバーを直接初期化するコンストラクターの本体で値を割り当てるより優先されます。 次の例では、メンバー初期化子リストがコロンの後のすべての identifier(argument) 式で構成されています。

    Box(int width, int length, int height)
        : m_width(width), m_length(length), m_height(height)
    {}

識別子はクラス メンバーを参照する必要があります。引数の値を使用して初期化されます。 引数には、コンストラクター パラメーター、関数呼び出し、またはstd::initializer_list T を指定 できます >。

const 参照型のメンバーとメンバーは、メンバー初期化子リストで初期化する必要があります。

派生コンストラクターを実行する前に、基本クラスが完全に初期化された状態を確保するには、パラメーター化された基本クラス コンストラクターの呼び出しを初期化子リストで行う必要があります。

既定のコンストラクター

通常、既定の コンストラクターにはパラメーターがありませんが、既定値を持つパラメーターを持つ場合があります。

class Box {
public:
    Box() { /*perform any required default initialization steps*/}

    // All params have default values
    Box (int w = 1, int l = 1, int h = 1): m_width(w), m_height(h), m_length(l){}
...
}

既定のコンストラクターは、特殊なメンバー 関数の 1 つです。 クラスでコンストラクターが宣言されている場合、コンパイラは暗黙的な既定のコンストラクター inline を提供します。

#include <iostream>
using namespace std;

class Box {
public:
    int Volume() {return m_width * m_height * m_length;}
private:
    int m_width { 0 };
    int m_height { 0 };
    int m_length { 0 };
};

int main() {
    Box box1; // Invoke compiler-generated constructor
    cout << "box1.Volume: " << box1.Volume() << endl; // Outputs 0
}

暗黙的な既定のコンストラクターに依存する場合は、前の例に示すように、クラス定義のメンバーを初期化してください。 これらの初期化子がない場合、メンバーは初期化解除され、Volume() 呼び出しによってガベージ値が生成されます。 一般に、暗黙的な既定のコンストラクターに依存しない場合でも、この方法でメンバーを初期化してください。

コンパイラは、削除済み として定義することで、暗黙的な既定のコンストラクターを 生成しません

    // Default constructor
    Box() = delete;

コンパイラによって生成された既定のコンストラクターは、クラス メンバーが既定で構築できない場合は、削除済みとして定義されます。 たとえば、クラス型のすべてのメンバーとそのクラス型メンバーには、アクセス可能な既定のコンストラクターとデストラクターが必要です。 参照型のすべてのデータ メンバーとメンバーには、既定のメンバー const 初期化子が必要です。

コンパイラによって生成された既定のコンストラクターを呼び出してかっこを使用すると、警告が発行されます。

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

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

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

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 box3; // C2512: no appropriate default constructor available
}

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

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

ただし、一連の初期化子リストを使用して、Box オブジェクトの配列を初期化できます。

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

詳細については、「初期化子」 を参照してください

コピー コンストラクター

コピー コンストラクターは 、同じ型のオブジェクトからメンバー値をコピーすることで、 オブジェクトを初期化します。 クラス メンバーがスカラー値などの単純型の場合は、コンパイラによって生成されたコピー コンストラクターで十分であり、独自の型を定義する必要はありません。 クラスでさらに複雑な初期化が必要な場合は、カスタム コピー コンストラクターを実装する必要があります。 たとえば、クラス メンバーがポインターの場合は、新しいメモリを割り当て、他のポイント先オブジェクトから値をコピーするコピー コンストラクターを定義する必要があります。 コンパイラによって生成されたコピー コンストラクターはポインターをコピーするだけで、新しいポインターは引き続き他のメモリ位置を指します。

コピー コンストラクターには、次のいずれかのシグネチャを指定できます。

    Box(Box& other); // Avoid if possible--allows modification of other.
    Box(const Box& other);
    Box(volatile Box& other);
    Box(volatile const Box& other);

    // Additional parameters OK if they have default values
    Box(Box& other, int i = 42, string label = "Box");

コピー コンストラクターを定義する場合は、コピー代入演算子 (=) も定義する必要があります。 詳細については、「代入コンストラクターと コピー コンストラクター 」および 「コピー代入演算子」を参照してください

コピー コンストラクターを deleted として定義することで、オブジェクトがコピーされるのを防ぐには、次のようにします。

    Box (const Box& other) = delete;

オブジェクトをコピーしようとすると、エラー C2280:削除された関数 を参照しようとしました。

コンストラクターの移動

移動 コンストラクターは 、元のデータをコピーせずに、既存のオブジェクトのデータの所有権を新しい変数に移動する特殊なメンバー関数です。 最初のパラメーターとして右辺値参照を受け取り、追加のパラメーターには既定値が必要です。 移動コンストラクターを使用すると、大きなオブジェクトを渡す際のプログラムの効率を大幅に向上させることができます。

Box(Box&& other);

コンパイラは、オブジェクトが破棄され、リソースが不要になった同じ型の別のオブジェクトによって初期化されている特定の状況で移動コンストラクターを選択します。 次の例は、オーバーロードの解決によって移動コンストラクターが選択されている場合の 1 つのケースを示しています。 を呼び出すコンストラクター get_Box() では、返される値は get_Box() (eXpiring 値) です。 変数に割り当てられていないため、スコープ外に出そうになります。 この例の動機を提供するために、Box に、その内容を表す文字列の大きなベクトルを指定しましょう。 移動コンストラクターは、ベクターとその文字列をコピーするのではなく、期限切れの値 "box" からそれを "盗む" ので、ベクターが新しいオブジェクトに属します。 クラスと クラスの両方が独自の移動コンストラクターを実装するために必要なのは、 の呼び std::movevector 出し string です。

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;

class Box {
public:
    Box() { std::cout << "default" << std::endl; }
    Box(int width, int height, int length)
       : m_width(width), m_height(height), m_length(length)
    {
        std::cout << "int,int,int" << std::endl;
    }
    Box(Box& other)
       : m_width(other.m_width), m_height(other.m_height), m_length(other.m_length)
    {
        std::cout << "copy" << std::endl;
    }
    Box(Box&& other) : m_width(other.m_width), m_height(other.m_height), m_length(other.m_length)
    {
        m_contents = std::move(other.m_contents);
        std::cout << "move" << std::endl;
    }
    int Volume() { return m_width * m_height * m_length; }
    void Add_Item(string item) { m_contents.push_back(item); }
    void Print_Contents()
    {
        for (const auto& item : m_contents)
        {
            cout << item << " ";
        }
    }
private:
    int m_width{ 0 };
    int m_height{ 0 };
    int m_length{ 0 };
    vector<string> m_contents;
};

Box get_Box()
{
    Box b(5, 10, 18); // "int,int,int"
    b.Add_Item("Toupee");
    b.Add_Item("Megaphone");
    b.Add_Item("Suit");

    return b;
}

int main()
{
    Box b; // "default"
    Box b1(b); // "copy"
    Box b2(get_Box()); // "move"
    cout << "b2 contents: ";
    b2.Print_Contents(); // Prove that we have all the values

    char ch;
    cin >> ch; // keep window open
    return 0;
}

クラスで移動コンストラクターが定義されていない場合、ユーザーが宣言したコピー コンストラクター、コピー代入演算子、移動代入演算子、またはデストラクターがない場合、コンパイラは暗黙的なコンストラクターを生成します。 明示的または暗黙的な移動コンストラクターが定義されていない場合、移動コンストラクターを使用する操作では、代わりにコピー コンストラクターが使用されます。 クラスが移動コンストラクターまたは移動代入演算子を宣言する場合、暗黙的に宣言されたコピー コンストラクターは deleted として定義されます。

暗黙的に宣言された移動コンストラクターは、クラス型のメンバーにデストラクターがない場合、またはコンパイラが移動操作に使用するコンストラクターを決定できない場合に、削除済みとして定義されます。

簡単ではない移動コンストラクターを記述する方法の詳細については、「移動コンストラクターと移動代入演算子 (C++)」を参照してください

明示的に既定のコンストラクターと削除されたコンストラクター

明示的 に既定の コピー コンストラクター、既定のコンストラクター、移動コンストラクター、コピー代入演算子、移動代入演算子、デストラクターを指定できます。 すべての特殊な メンバー関数 を明示的に削除できます。

class Box
{
public:
    Box2() = delete;
    Box2(const Box2& other) = default;
    Box2& operator=(const Box2& other) = default;
    Box2(Box2&& other) = default;
    Box2& operator=(Box2&& other) = default;
    //...
};

詳細については、「明示的に既定 の関数と削除された関数」を参照してください

constexpr コンストラクター

コンストラクターは、次の場合に constexpr として宣言できます

  • 既定として宣言されるか、一般的に constexpr 関数のすべての条件を満たします。
  • クラスには仮想基本クラスはありません。
  • 各パラメーターはリテラル型 です
  • 本体が関数 try-block ではありません。
  • すべての非静的データ メンバーと基本クラスのサブオブジェクトが初期化されます。
  • クラスが (a) バリアント メンバーを持つ共用体である場合、または (b) 匿名共用体を持つ場合、共用体メンバーの 1 つだけが初期化されます。
  • クラス型のすべての非静的データ メンバー、およびすべての基本クラスのサブオブジェクトには constexpr コンストラクターがあります

初期化子リスト コンストラクター

コンストラクターがパラメーターとしてstd::initializer_list T >を受け取り、その他のパラメーターに既定の引数がある場合、クラスが直接初期化によってインスタンス化されると、そのコンストラクターはオーバーロード解決で選択されます。 このメソッドを使用initializer_list受け入れ可能な任意のメンバーを初期化できます。 たとえば、Box クラス (前に示した) に メンバー が含むと std::vector<string> します m_contents 。 次のようなコンストラクターを指定できます。

    Box(initializer_list<string> list, int w = 0, int h = 0, int l = 0)
        : m_contents(list), m_width(w), m_height(h), m_length(l)
{}

次に、次のように Box オブジェクトを作成します。

    Box b{ "apples", "oranges", "pears" }; // or ...
    Box b2(initializer_list<string> { "bread", "cheese", "wine" }, 2, 4, 6);

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

クラスが 1 つのパラメーターを持つコンストラクターを含む場合、または 1 つを除くすべてのパラメーターに既定値がある場合は、パラメーター型をクラス型へ暗黙的に変換できます。 Box クラスに次のようなコンストラクターがある場合を例に説明します。

Box(int size): m_width(size), m_length(size), m_height(size){}

次のように、ボックスを初期化することができます。

Box b = 42;

または、int をボックスを受け取る関数に渡します。

class ShippingOrder
{
public:
    ShippingOrder(Box b, double postage) : m_box(b), m_postage(postage){}

private:
    Box m_box;
    double m_postage;
}
//elsewhere...
    ShippingOrder so(42, 10.8);

このような変換が便利な場合もありますが、微妙でありながら重大なコードのエラーにつながることが多くあります。 一般的な規則として、コンストラクター (およびユーザー定義演算子) で キーワードを使用して、この種の暗黙的な型変換を explicit 防ぐ必要があります。

explicit Box(int size): m_width(size), m_length(size), m_height(size){}

コンストラクターが明示的な場合、次の行ではコンパイラ エラーが発生します: ShippingOrder so(42, 10.8);。 詳細については、「ユーザー定義型 変換」を参照してください

構築順序

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

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

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

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

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

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

#include <iostream>

using namespace std;

class Contained1 {
public:
    Contained1() { cout << "Contained1 ctor\n"; }
};

class Contained2 {
public:
    Contained2() { cout << "Contained2 ctor\n"; }
};

class Contained3 {
public:
    Contained3() { cout << "Contained3 ctor\n"; }
};

class BaseContainer {
public:
    BaseContainer() { cout << "BaseContainer ctor\n"; }
private:
    Contained1 c1;
    Contained2 c2;
};

class DerivedContainer : public BaseContainer {
public:
    DerivedContainer() : BaseContainer() { cout << "DerivedContainer ctor\n"; }
private:
    Contained3 c3;
};

int main() {
    DerivedContainer dc;
}

出力は次のようになります。

Contained1 ctor
Contained2 ctor
BaseContainer ctor
Contained3 ctor
DerivedContainer ctor

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

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

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);
}

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

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

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

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

派生コンストラクターと拡張集計の初期化

基本クラスのコンストラクターがパブリックではないが、派生クラスからアクセスできる場合、空の中かっこを使用して、Visual Studio 2017 以降のモードで派生型のオブジェクトを初期化することはできません。 /std:c++17

次の例では、C++14 の準拠ビヘイビアーを示します。

struct Derived;

struct Base {
    friend struct Derived;
private:
    Base() {}
};

struct Derived : Base {};

Derived d1; // OK. No aggregate init involved.
Derived d2 {}; // OK in C++14: Calls Derived::Derived()
               // which can call Base ctor.

C++17 で、Derived は集約型と見なされるようになりました。 つまり、既定のプライベート コンストラクターによる Base の初期化は、集約初期化ルールの一部として直接実行されます。 以前、Base プライベート コンストラクターは Derived コンストラクターを介して呼び出され、friend 宣言があるために成功していました。

次の例は、Visual Studio 2017 以降のモードでの C++17 の動作を示 /std:c++17 しています。

struct Derived;

struct Base {
    friend struct Derived;
private:
    Base() {}
};

struct Derived : Base {
    Derived() {} // add user-defined constructor
                 // to call with {} initialization
};

Derived d1; // OK. No aggregate init involved.

Derived d2 {}; // error C2248: 'Base::Base': cannot access
               // private member declared in class 'Base'

複数の継承を持つクラスのコンストラクター

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

#include <iostream>
using namespace std;

class BaseClass1 {
public:
    BaseClass1() { cout << "BaseClass1 ctor\n"; }
};
class BaseClass2 {
public:
    BaseClass2() { cout << "BaseClass2 ctor\n"; }
};
class BaseClass3 {
public:
    BaseClass3() { cout << "BaseClass3 ctor\n"; }
};
class DerivedClass : public BaseClass1,
                     public BaseClass2,
                     public BaseClass3
                     {
public:
    DerivedClass() { cout << "DerivedClass ctor\n"; }
};

int main() {
    DerivedClass dc;
}

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

BaseClass1 ctor
BaseClass2 ctor
BaseClass3 ctor
DerivedClass ctor

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

委任 コンストラクターは、同 じクラス内の別のコンストラクターを呼び出して、初期化の作業の一部を実行します。 これは、すべてが同様の作業を実行する必要がある複数のコンストラクターがある場合に便利です。 1 つのコンストラクターにメイン ロジックを記述し、他のコンストラクターから呼び出すことができます。 次の簡単な例では、Box(int) は、その作業を Box(int,int,int) に委任します。

class Box {
public:
    // Default constructor
    Box() {}

    // Initialize a Box with equal dimensions (i.e. a cube)
    Box(int i) :  Box(i, i, i)  // delegating constructor
    {}

    // Initialize a Box with custom dimensions
    Box(int width, int length, int height)
        : m_width(width), m_length(length), m_height(height)
    {}
    //... rest of class as before
};

コンストラクターによって作成されたオブジェクトは、コンストラクターが終了するとすぐに完全に初期化されます。 詳細については、「コンストラクターの 委任」を参照してください

コンストラクターの継承 (C++11)

派生クラスは、次の例に示すように 宣言を使用して、直接の基本クラスからコンストラクター using を継承できます。

#include <iostream>
using namespace std;

class Base
{
public:
    Base() { cout << "Base()" << endl; }
    Base(const Base& other) { cout << "Base(Base&)" << endl; }
    explicit Base(int i) : num(i) { cout << "Base(int)" << endl; }
    explicit Base(char c) : letter(c) { cout << "Base(char)" << endl; }

private:
    int num;
    char letter;
};

class Derived : Base
{
public:
    // Inherit all constructors from Base
    using Base::Base;

private:
    // Can't initialize newMember from Base constructors.
    int newMember{ 0 };
};

int main()
{
    cout << "Derived d1(5) calls: ";
    Derived d1(5);
    cout << "Derived d1('c') calls: ";
    Derived d2('c');
    cout << "Derived d3 = d2 calls: " ;
    Derived d3 = d2;
    cout << "Derived d4 calls: ";
    Derived d4;
}

/* Output:
Derived d1(5) calls: Base(int)
Derived d1('c') calls: Base(char)
Derived d3 = d2 calls: Base(Base&)
Derived d4 calls: Base()*/

Visual Studio 2017以降: モードおよびそれ以降の ステートメントは、派生クラスのコンストラクターと同じシグネチャを持つコンストラクターを除き、基本クラスのすべてのコンストラクターのスコープに入ります。 /std:c++17 一般に、派生クラスが新しいデータ メンバーまたはコンストラクターを宣言しない場合は、コンストラクターの継承を使用することをお勧めします。

型が基底クラスを指定している場合、クラス テンプレートは型の引数からすべてのコンストラクターを継承できます。

template< typename T >
class Derived : T {
    using T::T;   // declare the constructors from T
    // ...
};

派生クラスは、複数の基底クラスに同じシグネチャを持つコンストラクターがある場合、複数の基底クラスからは継承できません。

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

クラス型のメンバーを含むクラスは、複合クラス と呼ばれるクラスです。 複合クラスのクラス型のメンバーが作成されると、そのコンストラクターはクラス自体のコンストラクターの前に呼び出されます。 含まれるクラスに既定のコンストラクターがないときは、複合クラスのコンストラクターで初期化リストを使用する必要があります。 前に示した 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"});
}

このセクションの内容

関連項目

クラスと構造体