C++のコピーコンストラクタを使ってみた

C++言語のコピーコンストラクタの機能を使用し、実際にその効果を検証してみました。

コピーコンストラクタが何故必要であるか?

C++言語を勉強し始めた頃はコピーコンストラクタはなかなか理解しにくいものでした。

まずどのような場合にコピーコンストラクタが必要かというと、同じクラスの他のオブジェクトで初期化する場合です。

例えばAというクラスがあり、そのインスタンスとしてaとbのオブジェクトを作成するとします。ただし、bのオブジェクトの初期化の際にはaのオブジェクトを渡して初期化するとします。

A a;
A b = a;

この場合、bのオブジェクトの参照先ヒープ領域の情報がaの領域で上書きされてしまいます。それにより何が起きるかというと、

  • bのオブジェクトが作成時に確保したヒープ領域の情報が失われてしまい、その領域を解放できなくなる
  • aとbの2つのオブジェクトが同じヒープ領域を参照するために、deleteでヒープ領域を解放する際に二重解放エラーとなってしまう

コピーコンストラクタを使用する事でこういった問題を回避することができます。

検証

では検証も交えて実際にコピーコンストラクタあり・なし時の動きの違いを見ていきましょう。

例えば、以下の例では、main関数内でクラスA型のaとbのオブジェクトを作成していますが、bのオブジェクトの作成時にaオブジェクトを渡して初期化しています。 この場合、上述したようにbのオブジェクトはaのヒープ領域を参照することになり、bが最初に確保したヒープ領域はもはや分からなくなります。これはメモリリークの原因になります。

コピーコンストラクタが無い場合:

#include 
#include 
using namespace std;

class A {
    private:
        const char *msg;
        char *ptr;
	public:
        A() { //コンストラクタ
            msg = "Hello";
            ptr = new char[100];
            strcpy(ptr, msg);
	    }

        const char* getMsg() {
            return ptr;
        }


        ~A() { //デストラクタ
            delete ptr;
        }
};

int main() {
	A a;
	A b = a; //bのメモリ領域の情報が失われてしまう

    cout << "Msg a: " << a.getMsg() << endl;
    cout << "Msg b: " << b.getMsg() << endl;

    return 0;
}

また、上記のプログラムを実行した際、aとbで動的に作成したptrのヒープ領域は同じであるため、デストラクタによりdeleteが実行された際に二重解放(double free)のエラーが発しています。(double freeは同じヒープ領域を2回解放しようとした際のエラーです)

実行結果:

Msg a: Hello
Msg b: Hello
free(): double free detected in tcache 2
Aborted

このような問題を回避する機能がコピーコンストラクタです。 コピーコンストラクタでは以下のようにコンストラクタの引数にconstでオブジェクトの参照を渡し初期化を行います。

Noteオブジェクトの参照をconstで渡すのは、引数として渡す側のオブジェクトの内容を誤って変更しないためです。この例の場合、bのオブジェクトの初期化時にA b = a;でaのオブジェクトの参照を渡しているため、万が一、b内でaのオブジェクトの内容を変更してしまうと予期しない問題が発生してしまう可能性が高い。

コピーコンストラクタがある場合:

#include 
#include 
using namespace std;

class A {
    private:
        const char *msg;
        char *ptr;
	public:
        A() { //コンストラクタ
            msg = "Hello";
            ptr = new char[100];
            strcpy(ptr, msg);
	    }

        A(const A &a) { //コピーコンストラクタ
            ptr = new char[100];
            strcpy(ptr, a.msg);
	    }

        const char* getMsg() {
            return ptr;
        }

        ~A() { //デストラクタ
            delete ptr;
        }
};

int main() {
	A a;
	A b = a; //コピーコンストラクタによりbのメモリ領域が使用される

    cout << "Msg a: " << a.getMsg() << endl;
    cout << "Msg b: " << b.getMsg() << endl;

    return 0;
}

上記のプログラムを実行した場合、aとbのヒープ領域は異なるため「double free detected」のエラーは発生しません。

実行結果:

Msg a: Hello
Msg b: Hello

C++の勉強方法に迷ったら、C++の生みの親が懇切丁寧に解説している以下の2冊の書籍を読むことをお勧めします。(入門書である「C++によるプログラミングの原則と実践」を先に読んでください)

公開日:2023年08月09日
最終更新日:2024年04月03日

他の記事も見る

このページのトップに戻る