本文へスキップ
BecomeCoder

C++コース · 第12章 演算子オーバーロードとコピー/ムーブ · レッスン50

コピーコンストラクタとコピー代入

ブラウザで完結

導入

オブジェクトを別のオブジェクトで初期化したり代入したりすると、「コピー」が起きます。ふつうはコンパイラが自動で行ってくれますが、クラスメモリなどの資源を持つときは、自分でコピーの仕方を定義しないと危険です。

説明

コピーが起きる場面は2つあります。生成時の初期化=「コピーコンストラクタ」、既存オブジェクトへの代入=「コピー代入演算子」です。

flowchart TB
  a["Buffer b2 = b1;<br/>コピーコンストラクタ(生成と同時)"]
  b["b3 = b1;<br/>コピー代入演算子(既存に代入)"]
  a --- b

自動生成されるコピーは「メンバをそのままコピー(浅いコピー)」です。ポインタで確保したメモリを持つ場合、これだと2つのオブジェクトが同じメモリを指してしまい、二重解放などの事故が起きます。そこで「深いコピー」を自分で書きます。

#include <iostream>
using namespace std;

class IntArray {
    int* data;
    int size;
public:
    IntArray(int n) : size(n) { data = new int[n](); }

    // コピーコンストラクタ:新しくメモリを確保して中身を複製(深いコピー)
    IntArray(const IntArray& other) : size(other.size) {
        data = new int[size];
        for (int i = 0; i < size; i++) data[i] = other.data[i];
    }

    // コピー代入演算子
    IntArray& operator=(const IntArray& other) {
        if (this == &other) return *this;   // 自己代入を防ぐ
        delete[] data;                      // 古いメモリを解放
        size = other.size;
        data = new int[size];
        for (int i = 0; i < size; i++) data[i] = other.data[i];
        return *this;
    }

    ~IntArray() { delete[] data; }
    void set(int i, int v) { data[i] = v; }
    int get(int i) const { return data[i]; }
};

int main() {
    IntArray a(3);
    a.set(0, 100);
    IntArray b = a;    // 深いコピー:別のメモリになる
    b.set(0, 999);
    cout << a.get(0) << " " << b.get(0) << endl;  // 100 999(独立)
    return 0;
}

まとめ

資源(生ポインタなど)を持つクラスは、コピーコンストラクタとコピー代入演算子で「深いコピー」を定義します。代入では「自己代入チェック」と「古い資源の解放」を忘れないこと。なお実務では、生ポインタ自体を避けて次章以降の vector やスマートポインタを使い、この手間をなくすのが最善です。

実際に動かしてみよう

下のエディタにC++を書いて「コンパイル & 実行」を押すと、ブラウザ内で本物のC++コンパイラ(clang + libc++)がその場でコンパイルして実行し、出力が表示されます。std::string・vector・クラス・テンプレート・STL などモダンC++がそのまま動きます。本文の例を書き換えて試しながら進めましょう(初回だけコンパイラの読み込みに時間がかかります。以降はキャッシュされます。この回は標準入力(cin)は使わず、値はコードに直接書きます)。

C/C++ — ブラウザ内で本物のコンパイラで実行

ブラウザ内で本物のC/C++コンパイラ(clang + libc++ / WebAssembly)を動かします。構造体・malloc/free・std::string・vector・クラス・テンプレート・STL などがそのまま実行できます(初回のみコンパイラの読み込みに時間がかかります。以降はブラウザにキャッシュされます)。
スクロールして表示された時点でも自動で読み込まれます。