代入演算子のオーバーロード
CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 44/104。
コピー代入演算子 (=) は、既存のオブジェクトを別のオブジェクトに代入するときに呼び出されます。コピーコンストラクタ(新しいオブジェクトを作成するもの)とは異なり、代入演算子は、すでに存在し、最初にクリーンアップが必要なリソースを保持している可能性があるオブジェクトに対して動作します。
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new int[s]) {}
~Buffer() { delete[] data; }
Buffer& operator=(const Buffer& other) {
if (this != &other) { // 1. 自己代入のチェック
delete[] data; // 2. 既存のリソースをクリーンアップ
size = other.size; // 3. データをコピー
data = new int[size];
for (size_t i = 0; i < size; ++i)
data[i] = other.data[i];
}
return *this; // 4. *this を返す
}
};自己代入のチェック (this != &other) は不可欠です。これがないと、b = b; はコピーを試みる前にデータを削除してしまい、未定義の動作を引き起こします。
この演算子は、a = b = c; のような連鎖を可能にするために、*this を参照によって返します。これは組み込み型の動作と一致します。
リソースを管理するクラスでは、コピー代入演算子とムーブ代入演算子の両方を実装することがよくあります:
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data; // 現在のリソースを解放する
data = other.data; // リソースを移動する
size = other.size;
other.data = nullptr; // 移動元を有効な状態に保つ
other.size = 0;
}
return *this;
}ムーブ代入演算子は、コピーする代わりに所有権を転送するため、vec[0] = createBuffer(); のような操作がはるかに効率的になります。
チャレンジ
簡単動的に割り当てられた文字配列を管理し、既存のオブジェクト間での代入を適切に処理する DynamicString クラスを作成しましょう。これは、メモリリークやダングリングポインタを発生させずに、あるオブジェクトから別のオブジェクトへ文字列データを安全に転送する必要がある実用的なシナリオです。
コードを整理するために、2つのファイルを作成します:
DynamicString.h: 動的メモリを使用してCスタイルの文字列を格納するDynamicStringクラスを定義します。クラスでは以下を管理する必要があります:- 文字列データ用のプライベートな
char*ポインタ - 長さ用のプライベートな
size_t const char*を受け取り、ディープコピーを作成するコンストラクタ- 割り当てられたメモリを適切に解放するデストラクタ
- 自己代入保護を備えたディープコピーを実行するコピー代入演算子
- 所有権を効率的に転送するムーブ代入演算子
- 内部文字列を返す
c_str()メソッド (const) - 文字列の長さを返す
length()メソッド (const)
コピー代入演算子は、自己代入のチェック、既存リソースのクリーンアップ、新しいデータのコピー、そして
*thisの返却という4つの必須ステップに従う必要があります。ムーブ代入演算子は、ソースのポインタを奪い、ソースを有効な空の状態(長さ0の nullptr)にする必要があります。- 文字列データ用のプライベートな
main.cpp: 入力から3つの文字列を読み込みます(それぞれ別の行)。3つのDynamicStringオブジェクトを作成します:最初の入力でstr1、2番目の入力でstr2、3番目の入力でstr3。代入演算子の動作を実演してください:
- 3つの文字列すべての初期状態を出力します
- コピー代入を使用します:
str1 = str2; - コピー代入後の str1 と str2 を出力します(両方とも同じ内容になるはずです)
- ムーブ代入を使用します:
str2 = std::move(str3); - ムーブ代入後の str2 と、str3 の長さを出力します(ムーブされた後の長さは 0 になるはずです)
出力形式:
Initial: str1: <value> str2: <value> str3: <value> After copy (str1 = str2): str1: <value> str2: <value> After move (str2 = std::move(str3)): str2: <value> str3 length: 0
strlen と strcpy のために <cstring> を使用してください。ムーブ代入演算子には noexcept を指定してください。std::move のために main.cpp に <utility> をインクルードしてください。ヘッダーファイルにはヘッダーガードを忘れずに含めてください。
自分で試してみよう
#include <iostream>
#include <string>
#include <utility>
#include "DynamicString.h"
using namespace std;
int main() {
// 入力から3つの文字列を読み込む
string input1, input2, input3;
getline(cin, input1);
getline(cin, input2);
getline(cin, input3);
// 3つのDynamicStringオブジェクトを作成する
DynamicString str1(input1.c_str());
DynamicString str2(input2.c_str());
DynamicString str3(input3.c_str());
// TODO: 3つの文字列すべての初期状態を出力する
// フォーマット:
// Initial:
// str1: <value>
// str2: <value>
// str3: <value>
// TODO: コピー代入を使用する: str1 = str2;
// TODO: コピー代入後のstr1とstr2を出力する
// フォーマット:
// After copy (str1 = str2):
// str1: <value>
// str2: <value>
// TODO: ムーブ代入を使用する: str2 = std::move(str3);
// TODO: ムーブ後のstr2と、str3の長さを出力する
// フォーマット:
// After move (str2 = std::move(str3)):
// str2: <value>
// str3 length: 0
return 0;
}このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
3コンストラクタとデストラクタ
デフォルトコンストラクタ引数付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ詳解Rule of Three / Five / Zeroまとめ - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () 演算子のオーバーロード型変換演算子まとめ:Matrix クラス