名前空間
変種
操作

3/5/0の法則

From cppreference.com
< cpp‎ | language
 
 
C++言語
全般
フロー制御
条件実行文
if
繰り返し文 (ループ)
for
範囲for (C++11)
ジャンプ文
関数
関数宣言
ラムダ式
inline指定子
動的例外仕様 (C++17まで*)
noexcept指定子 (C++11)
例外
名前空間
指定子
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
記憶域期間指定子
初期化
代替表現
リテラル
ブーリアン - 整数 - 浮動小数点数
文字 - 文字列 - nullptr (C++11)
ユーザー定義 (C++11)
ユーティリティ
属性 (C++11)
typedef宣言
型エイリアス宣言 (C++11)
キャスト
メモリ確保
クラス
クラス固有の関数プロパティ
explicit (C++11)
static

特殊メンバ関数
テンプレート
その他
 

目次

[編集] 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の法則の次の一般的な表現につながります。

C.21: コピー、ムーブ、またはデストラクタ関数を定義または=deleteする場合、それらすべてを定義または=deleteしなさい。

[編集] 外部リンク

  1. "Rule of Zero", R. Martinho Fernandes 08/15/2012
  2. "A Concern about the Rule of Zero", Scott Meyers, 3/13/2014.
English 日本語 中文(简体) 中文(繁體)