3/5/0の法則
目次 |
[編集] 3の法則
クラスがユーザー定義のデストラクタ、ユーザー定義のコピーコンストラクタ、またはユーザー定義のコピー代入演算子を必要とする場合、ほぼ確実にこれら3つすべてを必要とします。
C++はユーザー定義型のオブジェクトを様々な状況(値渡し/値戻し、コンテナの操作など)でコピーしたりコピー代入したりするため、これらの特殊メンバ関数は、アクセス可能であれば呼び出されます。ユーザー定義されていない場合、コンパイラによって暗黙的に定義されます。
クラスが非クラス型(生ポインタ、POSIXファイルディスクリプタなど)のハンドルであるリソースを管理し、そのデストラクタが何もしない、そしてコピーコンストラクタ/代入演算子が「シャローコピー」(基になるリソースを複製せずにハンドルの値をコピーする)を実行する場合、暗黙的に定義された特殊メンバ関数は使用すべきではありません。
#include <cstddef> #include <cstring> #include <iostream> #include <utility> class rule_of_three { char* cstring; // raw pointer used as a handle to a // dynamically-allocated memory block public: explicit rule_of_three(const char* s = "") : cstring(nullptr) { if (s) { cstring = new char[std::strlen(s) + 1]; // allocate std::strcpy(cstring, s); // populate } } ~rule_of_three() // I. destructor { delete[] cstring; // deallocate } rule_of_three(const rule_of_three& other) // II. copy constructor : rule_of_three(other.cstring) {} rule_of_three& operator=(const rule_of_three& other) // III. copy assignment { // implemented through copy-and-swap for brevity // note that this prevents potential storage reuse rule_of_three temp(other); std::swap(cstring, temp.cstring); return *this; } const char* c_str() const // accessor { return cstring; } }; int main() { rule_of_three o1{"abc"}; std::cout << o1.c_str() << ' '; auto o2{o1}; // II. uses copy constructor std::cout << o2.c_str() << ' '; rule_of_three o3("def"); std::cout << o3.c_str() << ' '; o3 = o2; // III. uses copy assignment std::cout << o3.c_str() << '\n'; } // I. all destructors are called here
出力
abc abc def abc
コピー可能なハンドルを介してコピー不可能なリソースを管理するクラスは、コピー代入演算子とコピーコンストラクタをprivateとして宣言し、その定義を提供しない(C++11まで)コピー代入演算子とコピーコンストラクタを= deleteとして定義する(C++11以降)必要があるかもしれません。これは3の法則の別の応用です。1つを削除して他のものを暗黙的に定義されたままにすることは、通常は誤りです。
[編集] 5の法則
ユーザー定義(= defaultまたは= delete宣言を含む)のデストラクタ、コピーコンストラクタ、またはコピー代入演算子が存在すると、ムーブコンストラクタとムーブ代入演算子の暗黙的な定義が妨げられるため、ムーブセマンティクスが望ましいクラスは、これら5つの特殊メンバ関数すべてを宣言する必要があります。
class rule_of_five { char* cstring; // raw pointer used as a handle to a // dynamically-allocated memory block public: explicit rule_of_five(const char* s = "") : cstring(nullptr) { if (s) { cstring = new char[std::strlen(s) + 1]; // allocate std::strcpy(cstring, s); // populate } } ~rule_of_five() { delete[] cstring; // deallocate } rule_of_five(const rule_of_five& other) // copy constructor : rule_of_five(other.cstring) {} rule_of_five(rule_of_five&& other) noexcept // move constructor : cstring(std::exchange(other.cstring, nullptr)) {} rule_of_five& operator=(const rule_of_five& other) // copy assignment { // implemented as move-assignment from a temporary copy for brevity // note that this prevents potential storage reuse return *this = rule_of_five(other); } rule_of_five& operator=(rule_of_five&& other) noexcept // move assignment { std::swap(cstring, other.cstring); return *this; } // alternatively, replace both assignment operators with copy-and-swap // implementation, which also fails to reuse storage in copy-assignment. // rule_of_five& operator=(rule_of_five other) noexcept // { // std::swap(cstring, other.cstring); // return *this; // } };
3の法則とは異なり、ムーブコンストラクタとムーブ代入演算子を提供しないことは、通常エラーではありませんが、最適化の機会を逃していることになります。
[編集] 0の法則
カスタムデストラクタ、コピー/ムーブコンストラクタ、またはコピー/ムーブ代入演算子を持つクラスは、排他的に所有権を扱うべきです(これは単一責任の原則に従います)。他のクラスはカスタムデストラクタ、コピー/ムーブコンストラクタ、またはコピー/ムーブ代入演算子を持つべきではありません[1]。
この法則は、C++ Core GuidelinesにもC.20: デフォルトの操作の定義を避けられるなら避けなさいとして記載されています。
class rule_of_zero { std::string cppstring; public: rule_of_zero(const std::string& arg) : cppstring(arg) {} };
基底クラスが多態的な使用を意図されている場合、そのデストラクタはpublicかつvirtualとして宣言する必要があるかもしれません。これにより暗黙的なムーブがブロックされ(そして暗黙的なコピーが非推奨になる)、特殊メンバ関数は= defaultとして定義する必要があります[2]。
class base_of_five_defaults { public: base_of_five_defaults(const base_of_five_defaults&) = default; base_of_five_defaults(base_of_five_defaults&&) = default; base_of_five_defaults& operator=(const base_of_five_defaults&) = default; base_of_five_defaults& operator=(base_of_five_defaults&&) = default; virtual ~base_of_five_defaults() = default; };
しかし、これによりクラスはスライスされやすくなるため、多態的なクラスはしばしばコピーを= deleteとして定義します(C++ Core GuidelinesのC.67: 多態的なクラスはpublicなコピー/ムーブを抑制すべきを参照)。これにより、5の法則の次の一般的な表現につながります。