C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++のコピーコンストラクタはいつ呼び出される?仕組みと具体例を解説

コピーコンストラクタとは

コピーコンストラクタとは、同じクラスの既存オブジェクトを使って新しいオブジェクトを初期化するための特殊なコンストラクタです。主に次のような場面で呼び出されます。

  • 同じ型の別オブジェクトからオブジェクトを初期化するとき
  • オブジェクトを関数の引数として値渡しするとき
  • 関数からオブジェクトを値として返すとき

クラス内でコピーコンストラクタが定義されていない場合、コンパイラが自動的にデフォルトのコピーコンストラクタを生成します。しかし、クラスがポインタ変数を持ち、動的メモリ確保を行っている場合には注意が必要です。デフォルトの「浅いコピー(シャローコピー)」では同じメモリ領域を複数のオブジェクトが共有してしまい、二重解放などの深刻なバグを招きます。そのため、このようなクラスでは独自のコピーコンストラクタを定義することが必須です。

コピーコンストラクタの最も一般的な形式は以下のとおりです。

classname (const classname &obj) {
    // コンストラクタの本体
}

ここで obj は、別のオブジェクトを初期化するために使われる元オブジェクトへの参照です。引数を const 参照で受け取ることで、余計なコピーを防ぎながら、元オブジェクトが変更されないことを保証しています。

サンプルコード①:関数への値渡しで呼び出されるケース

#include <iostream>
using namespace std;

class Line {
    public:
        int getLength( void );
        Line( int len ); // 通常のコンストラクタ
        Line( const Line &obj); // コピーコンストラクタ
        ~Line(); // デストラクタ

    private:
        int *ptr;
};

// コンストラクタを含むメンバ関数の定義
Line::Line(int len) {
    cout << "Normal constructor allocating ptr" << endl;

    // ポインタ用のメモリを確保
    ptr = new int;
    *ptr = len;
}

Line::Line(const Line &obj) {
    cout << "Copy constructor allocating ptr." << endl;
    ptr = new int;
    *ptr = *obj.ptr; // 値をコピー
}

Line::~Line(void) {
    cout << "Freeing memory!" << endl;
    delete ptr;
}

int Line::getLength( void ) {
    return *ptr;
}

void display(Line obj) {
    cout << "Length of line : " << obj.getLength() <<endl;
}

// プログラムの main 関数
int main() {
    Line line(10);
    display(line);
    return 0;
}

実行結果

Normal constructor allocating ptr
Copy constructor allocating ptr.
Length of line : 10
Freeing memory!
Freeing memory!

このプログラムでは、まず通常のコンストラクタによって line オブジェクトが生成されます。その後、display() 関数へ line値渡しする際にコピーコンストラクタが呼び出され、新しいオブジェクトが作成されます。関数の終了時に一時オブジェクトのデストラクタが実行され、さらに main 関数の終了時にもう一度デストラクタが呼ばれるため、「Freeing memory!」が2回表示されます。

サンプルコード②:既存オブジェクトから新しいオブジェクトを作成するケース

続いて、同じ型の既存オブジェクトを使って別のオブジェクトを初期化する例を見てみましょう。先ほどのコードに少し変更を加えたものです。

#include <iostream>
using namespace std;

class Line {
    public:
        int getLength( void );
        Line( int len ); // 通常のコンストラクタ
        Line( const Line &obj); // コピーコンストラクタ
        ~Line(); // デストラクタ

    private:
        int *ptr;
};

// コンストラクタを含むメンバ関数の定義
Line::Line(int len) {
    cout << "Normal constructor allocating ptr" << endl;

    // ポインタ用のメモリを確保
    ptr = new int;
    *ptr = len;
}

Line::Line(const Line &obj) {
    cout << "Copy constructor allocating ptr." << endl;
    ptr = new int;
    *ptr = *obj.ptr; // 値をコピー
}

Line::~Line(void) {
    cout << "Freeing memory!" << endl;
    delete ptr;
}

int Line::getLength( void ) {
    return *ptr;
}

void display(Line obj) {
    cout << "Length of line : " << obj.getLength() <<endl;
}

// プログラムの main 関数
int main() {
    Line line1(10);
    Line line2 = line1; // ここでもコピーコンストラクタが呼び出される

    display(line1);
    display(line2);
    return 0;
}

実行結果

Normal constructor allocating ptr
Copy constructor allocating ptr.
Copy constructor allocating ptr.
Length of line : 10
Freeing memory!
Copy constructor allocating ptr.
Length of line : 10
Freeing memory!
Freeing memory!
Freeing memory!

この例では、Line line2 = line1; の部分でもコピーコンストラクタが呼び出されます。さらに display() への値渡しのたびにコピーコンストラクタが実行されるため、合計3回のコピーコンストラクタ呼び出しが発生します。各オブジェクトが独立したメモリを持ち、それぞれのデストラクタで安全に解放されている点に注目してください。

まとめ:コピーコンストラクタが呼び出される主なタイミング

  • 同じ型のオブジェクトで別のオブジェクトを初期化するとき(例:Line line2 = line1;
  • オブジェクトを関数に値渡しするとき
  • 関数からオブジェクトを値として返すとき

ポインタメンバや動的メモリを持つクラスでは、ディープコピーを行う独自のコピーコンストラクタを必ず定義しましょう。また、C++11以降ではムーブコンストラクタの活用や、コピー禁止のための = delete 指定なども選択肢になります。状況に応じて適切なコピー戦略を選ぶことが、安全で効率的なC++プログラミングの鍵となります。

  1. 【C++入門】コピーコンストラクタと代入演算子の違いを徹底解説

    C++を学んでいると、コピーコンストラクタと代入演算子の違いで混乱することがよくあります。どちらもオブジェクトの複製に関わる仕組みですが、その動作や使用場面はまったく異なります。この記事では、両者の違いをわかりやすく解説します。 コピーコンストラクタとは オーバーロードされたコンストラクタの一種です。 既存のオブジェクトのデータや値を使って、新しく生成されるオブジェクトを初期化します。 既存のオブジェクトを基に新しいオブジェクトを作成する際に使用されます。 元のオブジェクトと新しいオブジェクトは、それぞれ独立したメモリ領域に格納されます。 クラス内でコピーコンストラクタが定義されていない場合

  2. C++で点集合の線対称(ラインリフレクション)を判定するアルゴリズム

    問題概要2次元平面上にn個の点が与えられます。このとき、y軸に平行な直線で全ての点を鏡映(反射)した結果が、元の点集合と完全に一致するような直線が存在するかどうかを判定します。言い換えれば、ある直線を対称軸として全ての点を反転させたとき、反転後の点の集合が元の集合と同一になるかを確認する問題です。例えば、入力が points = [[1,1],[-1,1]] の場合を考えてみましょう。この場合、x = 0 の直線(y軸)を対称軸とすると、点 (1,1) は (-1,1) へ、(-1,1) は (1,1) へと移ります。点集合全体としては変化がないため、出力は true となります。解法のポイン