Menu
CoddyTech

コピーコンストラクタ

CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 20/104。

コピーコンストラクターは、既存のオブジェクトのコピーとして新しいオブジェクトを作成します。同じ型の別のオブジェクトからオブジェクトを初期化するとき、オブジェクトを値渡しするとき、またはオブジェクトを値として返すときに呼び出されます。

コピーコンストラクターは、同じクラスのオブジェクトへの const 参照を受け取ります。参照でなければなりません。other を値渡しすると、それ自体のコピーが必要になり、コピーコンストラクターが何度も呼び出されて無限に続いてしまいます。コピーを作成しても元のオブジェクトは決して変更されないように、const が指定されています。

class Player {
    std::string name;
    int health;
public:
    Player(std::string n, int h) : name(n), health(h) {}
    
    // コピーコンストラクタ
    Player(const Player& other) {
        name = other.name;
        health = other.health;
    }
};

Player original("Hero", 100);
Player copy = original;    // コピーコンストラクタが呼び出される
Player another(original);  // こちらもコピーコンストラクタを呼び出す

コピーコンストラクターを定義しない場合、コンパイラーは浅いコピーを行うものを生成します。つまり、各メンバーの値をそのままコピーします。これは単純な型では問題なく機能しますが、クラスが動的メモリを管理している場合は問題が発生します。

class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s) {
        data = new int[size];
    }
    
    // ディープコピーコンストラクタ
    Buffer(const Buffer& other) : size(other.size) {
        data = new int[size];              // 新しいメモリを確保する
        for (size_t i = 0; i < size; i++)
            data[i] = other.data[i];       // 内容をコピーする
    }
    
    ~Buffer() { delete[] data; }
};

独自のコピーコンストラクターがなければ、両方のオブジェクトが同じメモリを指すことになります。一方が破棄されると、もう一方はダングリングポインターを持つことになります。ディープコピーでは、新しいメモリを割り当てて実際のデータをコピーするため、各オブジェクトがそれぞれ自身のリソースを所有できます。

challenge icon

チャレンジ

簡単

クラスが動的メモリを管理する場合に、深いコピーが重要であることを示すメッセージ履歴システムを作りましょう。メッセージを動的に確保した配列に格納するMessageLogクラスを作成し、各コピーが独立したメモリを持つように適切なコピーコンストラクターを実装します。

コードを整理するために、2つのファイルを作成します。

  • MessageLog.h: 動的メモリを使ってメッセージのコレクションを管理するMessageLogクラスを定義します。クラスには次のものを含めます。
    • プライベートメンバー: メッセージを格納する文字列配列へのポインター、メッセージの最大数を表すcapacity、現在のメッセージ数を表すcount
    • capacityを受け取り、ヒープ上に配列を確保し、countを0に初期化する引数付きコンストラクター
    • 深いコピーを行うコピーコンストラクター: 新しいメモリを確保し、コピー元オブジェクトからすべてのメッセージをコピーする
    • 確保したメモリを解放し、"MessageLog destroyed"を出力するデストラクター
    • 空きがあればメッセージを追加するaddMessage(string msg)メソッド
    • 現在のメッセージ数を返すgetCount()メソッド
    • 指定されたインデックスのメッセージを返すgetMessage(int index)メソッド
  • main.cpp: コピーコンストラクターが独立したコピーを作成することを示します。入力からcapacityの値と2つのメッセージを読み取り、次の処理を行います。
    • 入力されたcapacityでoriginalというMessageLogを作成する
    • 両方のメッセージをoriginalに追加する
    • コピーコンストラクターを使ってbackupというコピーを作成する
    • "New message"という3つ目のメッセージをoriginalだけに追加する
    • "Original count: <count>"を出力する
    • "Backup count: <count>"を出力する
    • backupのすべてのメッセージを1行に1つずつ出力する

入力形式は次のとおりです。

  • 1行目: capacity(整数)
  • 2行目: 1つ目のメッセージ(文字列)
  • 3行目: 2つ目のメッセージ(文字列)

深いコピーが正しく実装されていれば、backupのメッセージ数は2、originalのメッセージ数は3になり、それぞれが独立したメモリを持つことが確認できます。適切なコピーコンストラクターがなければ、どちらも同じ配列を共有し、一方への変更がもう一方にも影響してしまいます。

#include "MessageLog.h"を使って、ヘッダーファイルをmain.cppにインクルードしてください。

自分で試してみよう

#include <iostream>
#include <string>
#include "MessageLog.h"
using namespace std;

int main() {
    // 入力を読み取る
    int capacity;
    cin >> capacity;
    cin.ignore();
    
    string msg1, msg2;
    getline(cin, msg1);
    getline(cin, msg2);

    // TODO: 入力された容量を持つ、'original' という名前の MessageLog を作成する

    // TODO: 両方のメッセージを original に追加する

    // TODO: コピーコンストラクタを使用して、'backup' という名前のコピーを作成する

    // TODO: original にのみ "New message" を追加する

    // TODO: "Original count: <count>" を出力する

    // TODO: "Backup count: <count>" を出力する

    // TODO: backup のすべてのメッセージを1行に1つずつ出力する

    return 0;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: C++オンラインコンパイラ