コピーコンストラクタっているの？

# 導入

「コピーコンストラクタってなんのためにあるの？」という質問を受け、自分なりの回答を書いてみます。

# 結論

メモリを直接操作できるC++だからこそ必要。
そしてコピーコンストラクタを定義するときは、代入演算子(コピーオペレータ)も定義しよう。

# [前提] コピーの種類

コピーには大きく2種類ある。

## シャローコピー(Shallow copy)

参照をコピーするコピーです。
メンバ変数の参照をコピーします。

参照ををコピーするので高速ですが、他のインスタンスと共有することになるので、適切に管理することが必要です。
一方の変更を他方が参照することになります。
マルチスレッド環境ではスレッドセーフであるかも重要になります。

## ディープコピー(Deep copy)

実体をコピーするコピーです。
メンバ変数のインスタンスをコピーします。

新しいインスタンスを生成するため、低速ですが、コピー元とコピー先は別のインスタンスになります。
シャローコピーと異なり、一方の変更を他方は参照できません。

# C++でのコピー

## コピーの種類

シャローコピーに相当するのはポインタのコピーです。
あくまでポインタの持つアドレスがコピーされるだけで、ポインタの示す領域はコピーされません。

```C++:C++でのシャローコピー
char buffer[256];

char* ptrA = &(buffer[0]);

char* ptrB = ptrA; // ポインタのコピー。複製されるのはbuffer[256]へのポインタだけで、配列が複製されることはない。
```

ディープコピーに相当するのは構造体のコピーです。

```C++:C++でのディープコピー
typedef struct {
    char buffer[256];
} stBuffer;

stBuffer buffA;

stBuffer buffB = buffA; // 構造体のコピー。buffAの全領域がbuffBの領域へ複製される。
```

## C++のオブジェクトのコピーはシャローコピーかディープコピーか

どちらにもなれますし、どちらでもないものにもなれます。
クラスの設計者次第です。

```C++:シャローコピーするクラス
class SyallowCopy {
public:
    char* m_ptr;
    SyallowCopy(char * ptr) : m_ptr(ptr) { }
    SyallowCopy(const SyallowCopy& rhs) : m_ptr(rhs.ptr) { }
};

char buffer[256] = "バッファだよ";

SyallowCopy objA(&(buffer[0]));
SyallowCopy objB = objA; // ここでコピーコンストラクタを呼び出す
SyallowCopy objC(objA);  // これもコピーコンストラクタを呼び出す

// objA.m_ptrもobjB.m_ptrもobjC.m_ptrもbufferを示す

std::strcpy( objA.m_ptr, "変更したよ" );

// bufferが"変更したよ"という文字列になっている。
```

```C++:ディープコピーするクラス
class DeepCopy {
public:
    char m_buffer[256];

    DeepCopy(const char * ptr) { std::strcpy(&(m_buffer[0]), ptr); }
    DeepCopy(const DeepCopy& rhs) { std::strcpy(&(m_buffer[0]), &(rhs.m_buffer[0])); }
};

DeepCopy objA("バッファだよ");
DeepCopy objB = objA; // ここでコピーコンストラクタを呼び出す
DeepCopy objC(objA);  // これもコピーコンストラクタを呼び出す

// objA.m_bufferもobjB.m_bufferもobjC.m_bufferも
// "バッファだよ"という文字列が入っていますがすべて違う領域です。

std::strcpy( &(objA.m_buffer[0]), "変更したよ" );

// objA.m_bufferは"変更したよ"になっているが、
// objB.m_bufferもobjC.m_bufferは"バッファだよ"のまま
```

どちらでもないの例としてはstd::auto_ptrです。
std::auto_ptrはコピー元からコピー先にアドレスの所有権が移ります。
※std::auto_ptrはC++11で非推奨になっています。

```C++:auto_ptr
auto ptrA = std::auto_ptr<int>( new int() );

auto ptrB = ptrA; // ここでptrAからptrBに所有権が移る

// ptrA.get() は nullptr、ptrB.get() はnew int()で取得したヒープ領域のアドレス
```

## 自動生成されるコピーコンストラクタ

デフォルトコンストラクタと同じで、特段の指定がなければ自動生成してくれます。
自動生成される場合、すべてのメンバ変数がコピーされます。
この時のコピーは、組込み型の場合はメモリ上でコピーされますが、クラスの場合はコピーコンストラクタが呼び出されます。

## 自動生成のコピーコンストラクタだと問題になるケース

リソースを管理するクラスの場合、問題となるケースがあります。

```C++:自動生成のコピーコンストラクタだと問題になる
class ResourceManager {
public:
    char* m_ptr;
    ResourceManager(size_t size) : m_ptr(new char[size]) { }
    ~ResourceManager() { delete[] m_ptr; }
};

{
    ResourceManager objA(5); // new char[5]が呼ばれる。取得したヒープのアドレスをAddrAとする
    {
        ResourceManager objB = objA; // 自動生成のコピーコンストラクタが呼ばれる
        // objA.m_ptrもobjB.m_ptrもAddrAになっている
    } // objBのデストラクタが呼ばれ、AddrAが解放される
} // objAのデストラクタが呼ばれ、AddrAを解放するが、2重解放になってしまう
```

これはメモリなので分かりやすく例外が発生しますが、プロジェクト独自のリソースだったり、エラー処理が不十分だったりするともっとややこしい事態が発生し、解析に苦労することもあります。
※objAとobjBのデストラクタの間で、objBのデストラクタが解放したリソースを他で取得していたりすると・・・。

# まとめ

C++のオブジェクトのコピー処理は、クラス設計者が適切に設計する必要が場合ある。
なので、コピーコンストラクタでコピー処理を定義できるようになっている。
※代入演算子(コピーオペレータ)も同じ。

# 余談

ガーベージコレクタ(GC)がないからnew/deleteをちゃんと管理する必要があるんですよね。
GCがあるとリークしない(しにくい)からいいなぁ・・・。

でも、結局メモリ以外のリソース解放はGCがやってくれないからあんまりスマートでない対処が必要になってますよね・・・。
.NET FrameworkのDisposeメソッドとか。