C++のコピーコンストラクタとメモリ管理

コピーコンストラクタの動作原理

オブジェクト間の代入操作(例:`obj2 = obj1;`)は、デフォルトのコピーコンストラクタを呼び出します。この際、各オブジェクトは異なるメモリアドレスに配置され、メンバ変数の値がコピーされます。


#include <iostream>
using namespace std;

struct Person {
    int age;
    string name;
};

int main() {
    Person p1 = {10, "張三"};
    cout << "age: " << p1.age << " name: " << p1.name << endl;
    cout << "p1のアドレス: " << &p1 << endl;

    Person p2 = p1;
    cout << "p2のアドレス: " << &p2 << endl;

    return 0;
}

出力例:
age: 10 name: 張三
p1のアドレス: 0x7fff1a2db570
p2のアドレス: 0x7fff1a2db5a0

ユーザー定義のコピーコンストラクタ

自作のコピーコンストラクタを定義することで、オブジェクトのコピー時に特定の振る舞いを制御できます。ただし、代入演算子(`=`)によるコピーでは、ユーザー定義のコピーコンストラクタは呼ばれないことに注意が必要です。


#include <iostream>
using namespace std;

struct Person {
    int age;
    string name;

    Person() {
        cout << "無引数コンストラクタ" << endl;
    }

    Person(int age) : Person(age, "李四") {
        cout << "1引数コンストラクタ" << endl;
    }

    Person(int age, string name) : age(age), name(name) {
        cout << "2引数コンストラクタ" << endl;
    }

    // カスタムコピーコンストラクタ
    Person(const Person& other) {
        cout << "コピーコンストラクタ実行" << endl;
        this->age = other.age;
        this->name = other.name;
    }

    ~Person() {
        cout << "デストラクタ実行" << endl;
    }
};

int main() {
    Person p1{10, "張三"};
    Person p2 = p1;  // → カスタムコピーコンストラクタが呼び出される

    Person p3;
    p3 = p1;  // → カスタムコピーコンストラクタは呼ばれず、デフォルトのコピーが行われる

    return 0;
}

出力:
2引数コンストラクタ
コピーコンストラクタ実行
デストラクタ実行
デストラクタ実行

ポインタとコピーの誤解

ポインタの代入(例:`p2 = p1;`)は、オブジェクトのコピーではなく、アドレスのコピーです。したがって、コピーコンストラクタは実行されません。


#include <iostream>
using namespace std;

struct Person {
    int age;
    string name;

    Person() {
        cout << "無引数コンストラクタ" << endl;
    }

    Person(int age, string name) : age(age), name(name) {
        cout << "2引数コンストラクタ" << endl;
    }

    Person(const Person& other) {
        cout << "コピーコンストラクタ" << endl;
        this->age = other.age;
        this->name = other.name;
    }

    ~Person() {
        cout << "デストラクタ" << endl;
    }
};

int main() {
    Person* p1 = new Person(100, "張三");
    Person* p2 = p1;  // ポインタのアドレスコピー。コピーコンストラクタは呼ばれない

    delete p1;
    return 0;
}

const pointer と pointer const

指針の修飾子には以下の種類があります:

  • const int* ptr:値の変更不可(定数ポインタ)。アドレスの変更は可能。
  • int* const ptr:アドレスの変更不可(ポインタ定数)。値の変更は可能。
  • const int* const ptr:両方とも変更不可。

#include <iostream>
using namespace std;

int main() {
    int num = 9;
    int num2 = 10;

    // 定数ポインタ
    const int* ptr1 = &num2;
    // *ptr1 = 100; ❌ 値の変更は不可
    ptr1 = #  // ✅ アドレスの変更は可

    // ポインタ定数
    int* const ptr2 = &num2;
    *ptr2 = 100;  // ✅ 値の変更は可
    // ptr2 = # ❌ アドレスの変更は不可

    cout << "ptr1の値: " << *ptr1 << endl;

    return 0;
}

浅コピーと深コピーの違い

クラスに動的メモリ(例:`char*`)を持つ場合、浅コピー(アドレスのコピー)は問題を引き起こす可能性があります。以下は深コピーの実装例です。


class Student {
private:
    char* name;

public:
    Student(const Student& other) {
        // 浅コピー:同じアドレスを共有 → データ競合や重複解放
        // this->name = other.name;

        // 深コピー:新しいメモリ領域を確保し、データをコピー
        this->name = (char*)malloc(sizeof(char) * 10);
        strcpy(this->name, other.name);
    }

    ~Student() {
        if (name) free(name);
    }
};

可変長引数関数

関数に任意の数の引数を渡すために、`va_list` を使用します。`va_start` で初期化し、`va_arg` で値を取り出します。


#include <iostream>
#include <cstdarg>
using namespace std;

void printSum(int count, ...) {
    va_list args;
    va_start(args, count);

    for (int i = 0; i < count; ++i) {
        int value = va_arg(args, int);
        cout << "値: " << value << endl;
    }

    va_end(args);
}

int main() {
    printSum(3, 10, 20, 30);
    return 0;
}

タグ: C++ コピーコンストラクタ 深コピー 浅コピー 可変長引数

9月8日 09:59 投稿