12

クラスでmoveコンストラクターを禁止すると、ベクターで使用できなくなります。

class Foo
{
  public:
      Foo(int i) : i_(i) {}
      Foo(Foo&&) = delete;

      int i_;
};

int main()
{
    std::vector<Foo> foo;
    foo.push_back(Foo(1));
}

なんでそうなの?

4

2 に答える 2

39

概要

移動メンバーを削除しないでください。


コンパイラが完全にC++11に準拠していると仮定すると、moveコンストラクタを明示的に削除すると、暗黙的に次のように宣言されます。

Foo(const Foo&) = delete;
Foo& operator=(const Foo&) = delete;

つまり、移動コンストラクター(または移動代入演算子)を宣言し、コピーメンバーを宣言しない場合、それらは暗黙的に削除済みとして宣言されます。したがって、完全なクラスFooは次のようになります。

class Foo
{
  public:
      Foo(int i) : i_(i) {}
      Foo(Foo&&) = delete;
      Foo(const Foo&) = delete;             // implicitly declared
      Foo& operator=(const Foo&) = delete;  // implicitly declared

      int i_;
};

今それがvector<Foo>::push_back(Foo(1))必要です。 アクセス可能な移動コンストラクター、またはアクセス可能なコピーコンストラクターによっても満たされる可能性があります。しかし、どちらもありません。修正するには:FooMoveConstructibleMoveConstructibleFoo

class Foo
{
  public:
      Foo(int i) : i_(i) {}
      Foo(const Foo&) = default;
      Foo& operator=(const Foo&) = default;

      int i_;
};

つまり、デフォルトでコピーメンバーを作成し、削除された移動メンバーを削除します。

一般に、移動メンバーを明示的に削除することはお勧めできません。クラスをコピー可能で「移動可能」にしたくない場合は、C ++ 03の場合とまったく同じように宣言します。つまり、コピーメンバーを宣言/定義します。を使用してコピーメンバーをコンパイラで生成させることができますが= default、それでもユーザー宣言としてカウントされます。また、移動メンバーを宣言しないでください。存在しない移動メンバーは、削除された移動メンバーと同じではありません。

Foo移動メンバーが削除されたということは、コピーコンストラクターが正常に機能したとしても、右辺値からのコピーを作成できないことを意味します。これが望ましい意図であることはめったにありません。

クラスをコピー可能または移動可能にしたくない場合でも、コピーメンバーを削除し、移動メンバーを宣言しない(つまり、存在しない)ままにしておくことをお勧めします。コード(自分のコードを含む)をレビューしていて、削除された移動メンバーを見つけた場合、それらはほぼ間違いなく正しくないか、せいぜい不必要で混乱を招きます。

いつの日か、誰かが削除された移動メンバーの良いユースケースを思い付くでしょう。ただし、これはまれなユースケースになります。コードにそのようなパターンが見られる場合は、コードの作成者が非常に適切な説明をしていることを期待する必要があります。そうしないと、削除された移動メンバーが正しくない可能性があります(せいぜい不要です)。しかし、明るい面では、このエラーは実行時ではなくコンパイル時に表示されます(例のように)。

これは、特別なメンバーのいずれかを明示的に宣言したときにコンパイラーが暗黙的に実行することの要約チャートです。赤で表示されたこれらの正方形は、非推奨の動作を表しています。

ここに画像の説明を入力してください

= defaultそして、ユーザー宣言= deleteとしてカウントします。

スライドデッキ全体を表示したい場合は、ここをクリックしてください。

于 2013-03-03T01:58:49.217 に答える
0

あなたは言う:

私はもはやそれをベクトルで使用することはできません:

ただし、C ++ 11以降、ベクターで使用するための要件は最小限です。代わりに、各ベクトル演算には独自の要件があります。したがって、実際Fooにはベクターで使用できますが、オブジェクトの移動やコピーを要求する可能性のない操作に制限されます。

たとえば、次のように書くことができます。

std::vector<Foo> w(5);
w.pop_back();

また、電話w[0].some_member_function();をかけることもできます。

ただし、定義した方法により、次のいずれも記述できませんFoo

std::vector<Foo> w = { 1, 2, 3 };   // Requires moving elements out of initializer_list
w.resize(5);        // Resize may mean reallocation which may require elements to be moved to new location
w.push_back(5);     // ditto
w.erase(w.begin()); // Erasing an element may require other elements to move to fill its place
w[0] = 1;           // operator= is implicitly deleted as described in Howard's answer (but you could explicitly default it to make this work)

Fooコピー可能で移動可能ではなく、移動シナリオをコピーの使用にフォールバックさせたい場合、それを行う唯一の方法は、move-constructorをまったく宣言しないことです。ハワードの表に示すように、デストラクタ、コピーコンストラクタ、および/またはコピー代入演算子を宣言することにより、コンパイラによって生成されたmove-constructorを強制的に禁止します

この例については、ハワードの回答の最後のコードサンプルを参照してください。

明確にするために、実際には、moveコンストラクターにはいくつかの異なるステータスがありますが、すべてがハワードの表に示されているわけではありません。

  1. 削除済みとして定義され、ユーザー定義
  2. 削除済みとして定義され、ユーザー定義ではありません(これは、メンバーが移動できない場合に発生します)
  3. デフォルトおよびユーザー定義
  4. デフォルトであり、ユーザー定義ではありません
  5. ユーザー提供(つまり、ユーザー定義であり、デフォルトも削除もされていない)
  6. 宣言されていない

上記のケース1、3、4、5の場合、moveコンストラクターはオーバーロード解決によって検出されます。ケース2と6の場合、過負荷解決では検出されません。コピー/移動シナリオpush_back(Foo(1));は、コピーコンストラクター(存在する場合)にフォールバックします。

于 2016-03-04T00:47:06.060 に答える