Rule of Three / Five / Zero
CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 25/104。
クラスが動的メモリのようなリソースを管理する場合、カスタムの destructor、copy constructor、move constructor が必要だと学びました。しかし、どの特殊なメンバー functions を実装するべきか判断するのに役立つ指針があります。それがRule of Three, Five, and Zeroです。
Rule of Three は次のように定めています。これら3つのうちいずれかを定義する場合は、3つすべてを定義する必要があります。
- Destructor
- Copy constructor
- Copy assignment operator
Rule of Fiveは、ムーブ操作を追加することで、これを現代のC++向けに拡張したものです。
- デストラクタ
- コピーコンストラクタ
- コピー代入演算子
- ムーブコンストラクタ
- ムーブ代入演算子
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new int[s]) {}
~Buffer() { delete[] data; } // 1. デストラクタ
Buffer(const Buffer& other); // 2. コピーコンストラクタ
Buffer& operator=(const Buffer& other); // 3. コピー代入
Buffer(Buffer&& other) noexcept; // 4. ムーブコンストラクタ
Buffer& operator=(Buffer&& other) noexcept; // 5. ムーブ代入
};Rule of Zeroは最もシンプルなアプローチです。クラスがリソースを直接管理しない場合は、これらの関数をどれも定義しないでください。コンパイラーにそれらを生成させるか、リソースを代わりに管理してくれるスマートポインターや標準コンテナーを使用します。
class Player {
std::string name; // std::string は自身のメモリを管理します
std::vector<int> scores; // std::vector は自身のリソースを処理します
public:
Player(std::string n) : name(n) {}
// デストラクタ、コピー、またはムーブ関数は不要です!
};これらのルールに従うことで、一部の操作は定義されている一方で他の操作が欠けている場合に発生する、二重削除、メモリリーク、ダングリングポインタなどのバグを防げます。
チャレンジ
簡単TextBuffer classを構築し、Rule of Fiveに従って、動的に確保された文字データを適切に管理するための5つすべての特殊メンバー関数を実装しましょう。これにより、堅牢でリソースを管理するclassを作成するために、コピー操作とmove操作がどのように連携するかを示します。
コードを整理するために、2つのファイルを作成します。
TextBuffer.h: テキストを動的に確保された文字配列に格納するTextBufferclassをdefineします。classには次の要素が必要です。- Privateメンバー: テキストのcontent用の
dataという名前のchar*pointer、および文字列のlength(null終端文字を含まない)用のsize_t length - C-string(
const char*)を受け取り、メモリをAllocateし、contentをコピーして、"TextBuffer created: <text>"を出力するパラメーター付きconstructor - メモリを解放し(nullでない場合)、
"TextBuffer destroyed"を出力するdestructor - deep copyを実行し、
"TextBuffer copied"を出力するcopy constructor - self-assignmentを処理し、existing dataをクリーンアップしてdeep copyを実行し、
"TextBuffer copy-assigned"を出力するcopy assignment operator。*thisをReturnします - 所有権を移 transferし、
"TextBuffer moved"を出力する(noexceptが付けられた)move constructor。sourceは有効なempty状態のままにします - self-assignmentを処理し、existing dataをクリーンアップして所有権を移 transferし、
"TextBuffer move-assigned"を出力する(noexceptが付けられた)move assignment operator。*thisをReturnします - 格納されたテキストを返す
getText()method(dataがnullの場合はempty string""を返す) - lengthを返す
getLength()method
- Privateメンバー: テキストのcontent用の
main.cpp: 5つすべての特殊メンバー関数が動作する様子を示します。inputからテキスト文字列を読み取り、次の操作を行います。- input textを使って、
originalという名前のTextBufferをCreateします originalからcopy constructorを使ってcopiedをCreateします"Temporary"というテキストでanotherをCreateします- copy assignmentを使用します:
another = original std::move()を使ってoriginalからmove-constructしてmovedをCreateします"Target"というテキストでtargetをCreateします- move assignmentを使用します:
target = std::move(copied) "--- Final State ---"を出力します- 各buffer(original、copied、moved、another、target)について、
"original: <text> (length: <len>)"を出力します
- input textを使って、
moveの後、source objects(originalとcopied)にはlength 0のempty textが表示され、destination objectsには移 transferされたdataが保持されます。これはRule of Fiveの動作を示しています。つまり、安全なリソース管理を保証するために、5つすべてのfunctionsが連携して動作します。
strlenやstrcpyなどのstring functionsには<cstring>を、std::move()には<utility>をincludeします。
自分で試してみよう
#include <iostream>
#include <string>
#include <utility>
#include "TextBuffer.h"
using namespace std;
int main() {
string input;
getline(cin, input);
// TODO: 入力テキストで 'original' という TextBuffer を作成する
// TODO: 'original' からコピーコンストラクタを使って 'copied' を作成する
// TODO: テキスト "Temporary" で 'another' を作成する
// TODO: コピー代入を使用する: another = original
// TODO: std::move() を使って 'original' からムーブ構築して 'moved' を作成する
// TODO: テキスト "Target" で 'target' を作成する
// TODO: ムーブ代入を使用する: target = std::move(copied)
// TODO: "--- Final State ---" を出力する
// TODO: 各バッファの状態を次の形式で出力する:
// "original: <text> (length: <len>)"
// 出力対象: original, copied, moved, another, target
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
3コンストラクタとデストラクタ
デフォルトコンストラクタパラメータ付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ徹底解説Rule of Three / Five / Zero復習 - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () の演算子オーバーロード型変換演算子復習 - Matrixクラス自分で練習してみよう: C++オンラインコンパイラ