web-dev-qa-db-ja.com

3つのルールはC ++ 11で5つのルールになりますか?

そのため、右辺値の参照について このすばらしい講義 を見て、すべてのクラスがこのような「コンストラクターの移動」、template<class T> MyClass(T&& other)の恩恵を受けると思いましたeditおよびもちろん「移動代入演算子」、template<class T> MyClass& operator=(T&& other)は、Philippが答えを指摘しているように、メンバーが動的に割り当てられている場合、または一般的にポインターを格納します。あなたと同じようにshouldは、前述の点が当てはまる場合、コピーアクター、代入演算子、およびデストラクターを持っている必要があります。考え?

322
Xeo

3のルールは3、4、5のルールになります。

各クラスは、次の特別なメンバー関数のセットの1つを明示的に定義する必要があります。

  • None
  • デストラクタ、コピーコンストラクタ、コピー代入演算子

さらに、デストラクタを明示的に定義する各クラスは、移動コンストラクタおよび/または移動代入演算子を明示的に定義できます。

通常、次の特別なメンバー関数のセットのいずれかが賢明です。

  • なし(暗黙的に生成された特別なメンバー関数が正確かつ高速である多くの単純なクラスの場合)
  • デストラクタ、コピーコンストラクタ、コピー代入演算子(この場合、クラスは移動できません)
  • デストラクター、コンストラクターの移動、代入演算子の移動(この場合、クラスはコピーできません。基になるリソースがコピーできないリソース管理クラスに役立ちます)
  • デストラクタ、コンストラクタのコピー、代入演算子のコピー、コンストラクタの移動(コピー省略のため、代入演算子が値で引数を取る場合のオーバーヘッドはありません)
  • デストラクタ、コンストラクタのコピー、代入演算子のコピー、コンストラクタの移動、代入演算子の移動

移動コンストラクターと移動代入演算子は、他の特別なメンバー関数を明示的に宣言するクラスに対しては生成されず、コピーコンストラクターとコピー代入演算子は、移動コンストラクターまたは移動を明示的に宣言するクラスに対しては生成されないことに注意してください明示的に宣言されたデストラクタと暗黙的に定義されたコピーコンストラクタまたは暗黙的に定義されたコピー代入演算子を持つクラスは非推奨と見なされます。特に、次の完全に有効なC++ 03多相基底クラス

class C {
  virtual ~C() { }   // allow subtype polymorphism
};

次のように書き換える必要があります。

class C {
  C(const C&) = default;               // Copy constructor
  C(C&&) = default;                    // Move constructor
  C& operator=(const C&) = default;  // Copy assignment operator
  C& operator=(C&&) = default;       // Move assignment operator
  virtual ~C() { }                     // Destructor
};

少し面倒ですが、おそらく代替手段(すべての特別なメンバー関数の自動生成)よりも優れています。

ルールを順守しないと深刻な損害を引き起こす可能性があるビッグスリーのルールとは対照的に、ムーブコンストラクターとムーブ割り当て演算子を明示的に宣言しないことは一般に問題ありませんが、効率性に関しては最適ではないことがよくあります。前述のように、コンストラクターの移動と代入演算子の移動は、明示的にコピーコンストラクター、コピー代入演算子、またはデストラクターが宣言されていない場合にのみ生成されます。これは、コピーコンストラクターとコピー割り当て演算子の自動生成に関して、従来のC++ 03の動作と対称ではありませんが、はるかに安全です。したがって、移動コンストラクターと移動代入演算子を定義する可能性は非常に有用であり、新しい可能性(純粋に移動可能なクラス)を作成しますが、C++ 03のビッグスリールールに準拠するクラスは引き続き問題ありません。

リソース管理クラスの場合、基になるリソースをコピーできない場合は、コピーコンストラクターとコピー割り当て演算子を削除済み(定義としてカウント)として定義できます。多くの場合、まだコンストラクターの移動と代入演算子の移動が必要です。 C++ 03の場合のように、コピーと移動の代入演算子は、多くの場合swapを使用して実装されます。移動コンストラクターと移動代入演算子がある場合、汎用std::swapは移動コンストラクターと移動代入演算子が使用可能な場合はそれを使用するため、std::swapの特殊化は重要ではなくなります。

リソース管理(つまり、空ではないデストラクタがない)またはサブタイプのポリモーフィズム(つまり、仮想デストラクタがない)を目的としないクラスは、5つの特別なメンバー関数を宣言しないでください。それらはすべて自動生成され、正しく高速に動作します。

308
Philipp

this にリンクしている人がいないとは信じられません。

基本的に、記事は「Rule of Zero」を主張しています。記事全体を引用するのは適切ではありませんが、これが主なポイントだと思います。

カスタムデストラクタ、コピー/移動コンストラクタ、またはコピー/移動割り当て演算子を持つクラスは、所有権のみを扱う必要があります。他のクラスには、カスタムデストラクタ、コピー/移動コンストラクタ、またはコピー/移動代入演算子を使用しないでください。

また、このビットは私見重要です:

一般的な「パッケージの所有権」クラスは、標準ライブラリに含まれています:std::unique_ptrおよびstd::shared_ptr。カスタム削除オブジェクトを使用することにより、両方とも実質的にあらゆる種類のリソースを管理するのに十分な柔軟性を備えています。

65
NoSenseEtAl

私はそうは思わない、 つのルール は、次のいずれかを実装するがすべてではないクラスはおそらくバグだと述べる経験則です。

  1. コンストラクタをコピー
  2. 代入演算子
  3. デストラクタ

ただし、移動コンストラクターまたは移動代入演算子を省略しても、バグを意味するわけではありません。 最適化の機会を逃す可能性があります(ほとんどの場合)か、移動セマンティクスはこのクラスには関係ありませんが、これはバグではありません。

必要に応じて移動コンストラクターを定義することをお勧めしますが、必須ではありません。移動コンストラクターがクラス(std::complexなど)に関連せず、C++ 03で正常に動作するすべてのクラスが、移動を定義しなくてもC++ 0xで正常に動作し続ける多くの場合があります。コンストラクタ。

18
Motti

はい、そのようなクラスに移動コンストラクターを提供することは素晴らしいと思いますが、次のことを覚えておいてください。

  • 最適化にすぎません。

    コピーコンストラクタ、代入演算子、またはデストラクタの1つまたは2つだけを実装すると、おそらくバグが発生しますが、移動コンストラクタがないと、パフォーマンスが低下する可能性があります。

  • 移動コンストラクターは、変更なしで常に適用できるとは限りません。

    一部のクラスには常にポインターが割り当てられているため、そのようなクラスは常にデストラクター内のポインターを削除します。これらの場合、ポインターが割り当てられているか、移動されたか(nullになった)かどうかを確認するために、追加のチェックを追加する必要があります。

14
peoro

以下は、11年1月24日以降の現在の状況と関連する開発の簡単な更新です。

C++ 11標準(付録Dの[depr.impldec]を参照)によると:

クラスにユーザー宣言されたコピー割り当て演算子またはユーザー宣言されたデストラクタがある場合、コピーコンストラクタの暗黙的な宣言は廃止されます。クラスにユーザー宣言のコピーコンストラクターまたはユーザー宣言のデストラクターがある場合、コピー割り当て演算子の暗黙的な宣言は廃止されます。

廃止された動作を廃止するために、実際に 提案 でしたC++ 14に、従来の「3つの規則」ではなく、真の「5つの規則」を与えました。 2013年、EWGは、 C++ 2014。提案の決定の主な根拠は、既存のコードの破壊に関する一般的な懸念に関係していました。

最近、 提案されました 再びC++ 11の文言を適応させて、非公式の5つの規則、つまり

これらの関数のいずれかがユーザー提供の場合、コピー関数、移動関数、またはデストラクターはコンパイラー生成されません。

EWGによって承認された場合、「ルール」はC++ 17に採用される可能性があります。

8
Andrey Rekalo

基本的には次のようになります。移動操作を宣言しない場合は、3つのルールを尊重する必要があります。移動操作を宣言する場合、コンパイラー生成操作の生成は非常に制限されているため、3つのルールに「違反」しても害はありません。移動操作を宣言せず、3つのルールに違反した場合でも、C++ 0xコンパイラーは、1つの特別な関数がユーザー宣言され、他の特別な関数が非推奨となった「C++ 03互換性ルール」。

この規則はもう少し重要ではなくなると言っても安全だと思います。 C++ 03の本当の問題は、異なるコピーセマンティクスを実装するには、ユーザーがall関連する特殊関数を宣言する必要があったため、それらのいずれもコンパイラー生成されないことです(そうでなければ、間違った動作をします)。ただし、C++ 0xは、特別なメンバー関数の生成に関するルールを変更します。ユーザーがコピーセマンティクスを変更するためにこれらの関数の1つだけを宣言すると、コンパイラが残りの特別な関数を自動生成できなくなります。宣言が欠落していると、実行時エラーがコンパイルエラー(または少なくとも警告)に変わるため、これは良いことです。 C++ 03互換性の尺度として、一部の操作が引き続き生成されますが、この生成は非推奨と見なされ、少なくともC++ 0xモードで警告を生成するはずです。

コンパイラーが生成した特殊関数とC++ 03の互換性に関するかなり制限された規則により、3つの規則は3つの規則のままです。

以下は、最新のC++ 0xルールで問題ないはずの例です。

template<class T>
class unique_ptr
{
   T* ptr;
public:
   explicit unique_ptr(T* p=0) : ptr(p) {}
   ~unique_ptr();
   unique_ptr(unique_ptr&&);
   unique_ptr& operator=(unique_ptr&&);
};

上記の例では、他の特別な関数を削除済みとして宣言する必要はありません。制限的なルールのために、単に生成されません。ユーザーが宣言した移動操作が存在すると、コンパイラーが生成したコピー操作が無効になります。しかし、このような場合:

template<class T>
class scoped_ptr
{
   T* ptr;
public:
   explicit scoped_ptr(T* p=0) : ptr(p) {}
   ~scoped_ptr();
};

現在、C++ 0xコンパイラーは、コンパイラーが生成した可能性のある誤った操作を行う可能性のあるコピー操作に関する警告を生成することが期待されています。ここでは、3つのルールが重要であり、尊重される必要があります。この場合の警告は完全に適切であり、ユーザーにバグを処理する機会を与えます。削除された関数を使用して問題を取り除くことができます:

template<class T>
class scoped_ptr
{
   T* ptr;
public:
   explicit scoped_ptr(T* p=0) : ptr(p) {}
   ~scoped_ptr();
   scoped_ptr(scoped_ptr const&) = delete;
   scoped_ptr& operator=(scoped_ptr const&) = delete;
};

したがって、C++ 03の互換性のために、3つのルールがここでも適用されます。

4
sellibitze

3の規則が4の規則(または5)になったと言うことはできません。3の規則を強制し、いかなる形式の移動セマンティクスも実装しない既存のコードをすべて壊すことなく。

3のルールは、1つを実装する場合、3つすべてを実装する必要があることを意味します。

また、自動生成された移動があることを認識していません。 「3つの規則」の目的は、それらが自動的に存在し、1つを実装する場合、他の2つのデフォルト実装が間違っている可能性が高いためです。

3
CashCow

一般的な場合、はい、3のルールは5のルールになり、移動代入演算子と移動コンストラクターが追加されました。ただし、allクラスはコピー可能で移動可能ではなく、一部は移動可能です、一部は単にコピー可能です。

2
Puppy