導入
オブジェクトを別のオブジェクトで初期化したり代入したりすると、「コピー」が起きます。ふつうはコンパイラが自動で行ってくれますが、クラスがメモリなどの資源を持つときは、自分でコピーの仕方を定義しないと危険です。
説明
コピーが起きる場面は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 やスマートポインタを使い、この手間をなくすのが最善です。