コピー制御
[第]29[課では]コンストラクタ[の応用]について学びました。
[ただし、もし][objectをコピー]、[valueobjectに代入]、[objectを移動]する場合、C++には[一連の][コピーに関するルール]があります。
[このルールを理解して]、[初めて正しい]クラスを作成できる。
1. [三五の法則](Rule of Three/Five)
(1) 1.1 [三五法則]とは何か?
[もしあなたの]クラス[が以下のいずれかの]関数を[カスタマイズする必要がある]場合、[その際は][関連するすべての]関数を[カスタマイズしなければなりません]:
| [バージョン] | [管理が必要な]関数 | [説明] |
|---|---|---|
| [三法則](C++98) | 1. デストラクタbr 2. コピーコンストラクタbr 3. コピー代入演算子 |
[動的メモリ][を使用する場合に必要] |
| [五つの法則](C++11) | [三つの法則] +br4. 移動コンストラクタbr5. 移動代入演算子 |
C++11 [導入]移動セマンティクス |
(2) 1.2 [なぜ「3-5の法則」が必要なのか]?
[そのうちの1つをカスタマイズする必要がある場合]、[説明]クラス[内には][特別な処理が必要なリソース]([例えば]動的メモリ、ファイル[ハンドル])が含まれています。
例:[三法則に従わないことによる結果] (難易度 ⭐⭐)
▶ サンプル 2: codeexample (難易度 ⭐)
#include iostream
#include cstring
class BadString {
private:
char* data;
public:
// ...
BadString(const char* s) {
data = new char[strlen(s) + 1];
strcpy(data, s);
}
// ❌ YesDefinecopy constructor、value、destructor
// ()
};
int main() {
BadString s1("Hello");
BadString s2 = s1; // ❌:s1.data and s2.data
// main,s2 ( data)
// s1 ( data,)→!
return 0;
}
[結果]: プログラムが[クラッシュ]する([同一のメモリ領域を繰り返し解放]するため)。
[修正]: [3つの法則に従う]、[3つすべてをカスタマイズする]関数。
2. コピー代入演算子 *
基本的な使い方
#include iostream
#include cstring
class MyString {
private:
char* data;
public:
// ...
MyString(const char* s) {
data = new char[strlen(s) + 1];
strcpy(data, s);
}
// ...
MyString(const MyString& other) {
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
}
// ...
MyString& operator=(const MyString& other) {
if (this != &other) { // ...
delete data; // ...
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
}
return *this;
}
// ...
~MyString() {
delete data;
}
};
int main() {
MyString s1("Hello");
MyString s2("World");
s2 = s1; // ✅ callingvalue
return 0;
}
💡 [重点]: 代入演算子のコピー[確認要][自己代入]値(if (this != &other))。
3. 移動セマンティクス(C++11)*
(1) 3.1 なぜ[移動]が必要なのでしょうか?
[もし][一時的な]オブジェクト([まもなく破棄される]もの)があるなら、[その内容をコピーするのは無駄]だ――[それよりも]そのリソースを「[盗む]」ほうがよい。
例:[移動を使って不要なコピーを避ける] (難易度 ⭐⭐)
#include iostream
#include cstring
class MyString {
private:
char* data;
public:
// (C++11)
MyString(MyString&& other) noexcept {
data = other.data; // ...
other.data = nullptr; // other
}
// (C++11)
MyString& operator=(MyString&& other) noexcept {
if (this != &other) {
delete data; // ...
data = other.data; // ...
other.data = nullptr; // other
}
return *this;
}
};
int main() {
MyString s1("Hello");
MyString s2 = std::move(s1); // ✅ callingconstructor
// s1.data nullptr,s2.data
return 0;
}
💡 [ヒント]: std::move() [lvalueを]rvalue参照に[変換]することで、[moveコンストラクタ[または]move代入演算子]を[呼び出す]。
4. = デフォルト [和] = 削除*
(1) 4.1 [用] = デフォルト [让]コンパイル[器がデフォルトの実装を生成する]*
[もしあなたの]クラス[に特別なコピーロジックが必要ない場合]、= default [を使って]コンパイラに[デフォルトのものを]生成させることができます。
#include iostream
#include string
class Student {
private:
std::string name;
int age;
public:
//、、
Student(const Student& other) = default;
Student& operator=(const Student& other) = default;
~Student() = default;
};
int main() {
Student s1;
Student s2 = s1; // ✅ callingdefault copy constructor
return 0;
}
💡 [ヒント]: [もし]クラス[の中に] STL 型(std::string、std::vector [など])しか含まれていない場合は、= default [を使えば] [十分]です。
(2) 4.2 [用] = delete [コピー禁止]*
[もし]class[がコピーされるのを防ぎたい場合]([シングルトンパターンなど])、= delete [を使用して] [コピーを禁止]することができます。
#include iostream
class Singleton {
private:
Singleton() {} // ...
public:
// ...
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton& getInstance() {
static Singleton instance;
return instance;
}
};
int main() {
Singleton& s = Singleton::getInstance();
// Singleton s2 = s; // ❌ compilation:copy constructor
return 0;
}
5. 演習:[完全版] MyString クラス*
▶ サンプル 1: [完全な] MyString クラスを実装する (難易度 ⭐⭐⭐)
#include iostream
#include cstring
class MyString {
private:
char* data;
int length;
public:
// ...
MyString(const char* s = "") {
length = strlen(s);
data = new char[length + 1];
strcpy(data, s);
}
// ...
MyString(const MyString& other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
}
// ...
MyString(MyString&& other) noexcept {
data = other.data;
length = other.length;
other.data = nullptr;
other.length = 0;
}
// ...
MyString& operator=(const MyString& other) {
if (this != &other) {
delete data;
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
}
return *this;
}
// ...
MyString& operator=(MyString&& other) noexcept {
if (this != &other) {
delete data;
data = other.data;
length = other.length;
other.data = nullptr;
other.length = 0;
}
return *this;
}
// ...
~MyString() {
delete data;
}
// ...
void print() const {
if (data) std::cout << data;
}
int size() const {
return length;
}
};
int main() {
MyString s1("Hello");
MyString s2 = s1; // ...
s1.print(); // Hello
std::cout << std::endl;
s2.print(); // Hello
std::cout << std::endl;
return 0;
}
❓ よくある質問
Q:[どのような場合に]コピーコンストラクタを定義すべきか? A:[クラス内に]([動的メモリを]指す)ポインタ型のメンバがある[場合]。
クラスに基本型(
int、double)[または] STL型(std::string、std::vector)しか含まれていない場合は、[独自に実装する必要はない]――コンパイラが[自動的に正しく動作するコードを生成する](STL型は[ディープコピーを独自に実装している])。
Q:=default [和] =delete [どんな役割がある]? A:> -
= default:コンパイラに[デフォルトの][実装]([メンバーごとの]コピー)を[生成]させる > -= delete:[特定の]関数を[無効化]する([コピーの禁止]など)[推奨]
= default[手書きのコピーロジックの代替]([特別なロジックが必要ない場合])。
Q:[コピーコンストラクタを記述しない場合]、[どうなる]? A:コンパイラが[自動的に][デフォルトの]コピーコンストラクタを生成する([メンバーごとに]コピーを行う)。
[ただし、]クラス[に]ポインタのメンバがある場合、[デフォルトでは][浅いコピー]が行われ([ポインタの]値のみがコピーされ、[ポインタが指す内容はコピーされない])、[その結果、メモリが重複して解放されてしまう]。
📖 まとめ
- [三法則]:[デストラクタ]、[コピーコンストラクタ]、[コピー代入]のいずれかを[カスタマイズする必要がある場合]、[3つすべてをカスタマイズしなければならない]
- [5つの法則]:C++11 [以降]では、[移動コンストラクタと]移動代入も追加される
- [用]
= default[コンパイラにデフォルトの実装を生成させる] - [用]
= delete[特定の]関数を禁止する - 移動セマンティクス[不要なコピーを回避できる]、[効率が向上する]
📝 練習問題
- 初心者(難易度 ⭐):
Pointというクラスを定義し、xとyの[2つの]メンバを[含む]。
- [3つの法則に従う]、[独自に]コピーコンストラクタ、コピー代入演算子、デストラクタを実装する
main[でコピーをテスト]
- 中級(難易度 ⭐⭐):
[定義する]
DynamicArrayクラス([動的]配列)、[以下を含む]:
int* data([ヒープ上の]配列)int size(array[大小])
[5つのルールに従い]、[すべての特殊な]メンバ関数を実装する。
- 上級(難易度 ⭐⭐⭐):
[拡張]
DynamicArrayクラス、[追加]:
void push_back(int x):[配列の末尾に要素を追加]([容量が不足している場合]、[元の]2[倍]に拡張)void pop_back():[末尾の要素を削除]
- コピーコンストラクタ:[同]typeobject[を基に新しい]objectを初期化する
- 代入演算子のコピー:= 演算子のオーバーロード
- [3]/[5の法則]:[1つをカスタマイズする場合、通常はすべてをカスタマイズする必要がある]
- [浅いコピー] 対 [深いコピー]:ポインタ型メンバー[深いコピーが必要]
- delete 関数[コピーを阻止]:= delete
6. 🚀 次は
[コピー制御と]moveセマンティクスを学んだので、[次は] 継承([第]31[回])を学びます―― [これにより]クラス[間で]「[親子]」[関係]を形成し、[コードの][再利用]を実現します!



