Menu
Coddy logo textTech

コピーコンストラクタ

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

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

コピーコンストラクターは、同じクラスのオブジェクトへの 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; }
};

カスタムコピーコンストラクターがない場合、both オブジェクトは同じメモリを指すことになります。一方がdestroyedされると、もう一方にはダングリングポインターが残ります。深いコピーは新しいメモリを確保し、実際のdataをコピーすることで、各オブジェクトがそれぞれ自身のリソースを所有するようにします。

challenge icon

チャレンジ

簡単

クラスが動的メモリを管理する場合に、ディープコピーの重要性を示すメッセージ履歴システムを構築しましょう。動的に確保された配列にメッセージを格納する MessageLog クラスを作成し、それぞれのコピーが独立したメモリを持つように、適切なコピーコンストラクターを実装します。

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

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

    // TODO: Print "Original count: <count>"

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

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

    return 0;
}
quiz icon腕試し

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

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

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