コピーコンストラクタの動作原理
オブジェクト間の代入操作(例:`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;
}