C++のコンストラクタとデストラクタについて解説

C++において、コンストラクタとデストラクタは非常に重要な役割を担っています。

コンストラクタはオブジェクトが生成された際に自動的に呼び出され、初期化処理を行います。

一方、デストラクタはオブジェクトが破棄される際に自動的に呼び出され、メモリの解放やリソースの解放などの後処理を行います。

この記事では、コンストラクタとデストラクタの基礎から応用まで解説していきます。

目次から探す

C++のコンストラクタとは

C++において、コンストラクタはオブジェクトが生成される際に自動的に呼び出される特別なメソッドです。コンストラクタは、オブジェクトの初期化を行うために使用されます。

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

デフォルトコンストラクタは引数を持たないコンストラクタであり、何も処理を行わない場合でも自動的に呼び出されます。以下はデフォルトコンストラクタの例です。

class MyClass {
public:
    MyClass() {
        // 何も処理を行わない
    }
};

パラメータ付きコンストラクタ

パラメータ付きコンストラクタは、引数を受け取ってオブジェクトを初期化するためのコンストラクタです。以下はパラメータ付きコンストラクタの例です。

class MyClass {
public:
    int value;
    MyClass(int v) {
        value = v;
    }
};

//valueが10で初期化される
MyClass m = MyClass(10);

このように、パラメータ付きコンストラクタでは引数を受け取り、その値でオブジェクトを初期化します。

コピーコンストラクタ

コピーコンストラクタは、同じ型の別のオブジェクトから新しいオブジェクトを作成するための特別なコンストラクタです。以下はコピーコンストラクタの例です。

class MyClass {
public:
    int value;
    MyClass(const MyClass& other) {
        value = other.value;
    }
};

MyClass m1;
m1.value = 10;
//クラスのコピーが簡単になる
MyClass m2 = MyClass(m1);

このように、コピーコンストラクタでは同じ型の別のオブジェクトから値を受け取り、それらと同じ値で新しいオブジェクトを初期化します。

C++のデストラクタとは

C++のデストラクタは、オブジェクトが破棄される際に呼び出される関数です。デストラクタは、オブジェクトが使用していたリソースを解放するために使用されます。

デストラクタの基本的な使い方

デストラクタは、次のように定義されます。

class MyClass {
public:
    ~MyClass() {
        // リソースの解放処理
    }
};

上記の例では、MyClassという名前のクラスにデストラクタが定義されています。デストラクタは、~で始まり、その後にクラス名が続きます。

以下は、オブジェクトを生成し、それを破棄する例です。

int main() {
    MyClass obj; // オブジェクトの生成
    // 何らかの処理
} // スコープを抜けるとオブジェクトが破棄される

上記の例では、MyClass型のオブジェクトを生成しました。

このオブジェクトは、main関数内で使用された後に自動的に破棄されます。この時点で、コンパイラーは自動的にデストラクタを呼び出します。

仮想デストラクタ

仮想デストラクタとは、ポリモーフィズム(多態性)を実現するために使用される特殊な種類のデストラクタです。

仮想デストラクタを持つ基底(親) クラスから派生(子) クラスを作成した場合、派生(子) クラスでも仮想デストラクタを定義する必要があります。

仮装デストラクタを定義することで、Derivedクラスの実体をBaseクラスのポインタに参照させた状態で破棄しても、正しくDerivedクラスのデストラクタも呼ばれるようになります。

以下は仮想デストラクタを持つ基底(親) クラスと派生(子) クラスの例です:

#include <iostream>

class Base {
public:
    virtual ~Base() { std::cout << "Baseのデストラクタが呼ばれました" << std::endl; }
};

class Derived : public Base {
public:
    ~Derived() { std::cout << "Derivedのデストラクタが呼ばれました" << std::endl; }
};

int main()
{
    Base* c1 = new Derived;
    delete c1;
    
    return 0;
}
Derivedのデストラクタが呼ばれました
Baseのデストラクタが呼ばれました

こちらは、Derivedクラスのオブジェクトを動的に作成して基底クラスのBaseに代入、その後破棄していますが、破棄する際に両方のデストラクタが正しく呼ばれています。

ですが、もしvirtual ~Base()virtualを外して~Base()と定義していたらどうなるでしょうか。

#include <iostream>

class Base {
public:
    ~Base() { std::cout << "Baseのデストラクタが呼ばれました" << std::endl; }
};

class Derived : public Base {
public:
    ~Derived() { std::cout << "Derivedのデストラクタが呼ばれました" << std::endl; }
};

int main()
{
    Base* c1 = new Derived;
    delete c1;
    
    return 0;
}
Baseのデストラクタが呼ばれました

c1にはDerivedクラスの実体を代入しているにも関わらず、破棄する際にはBaseクラスのデストラクタしか呼ばれていません。

このような挙動を防止するためにあるのがvirtualキーワードを付けてデストラクタを定義する仮装デストラクタとなります。

目次から探す