18

gcc から奇妙なエラーが発生しましたが、その理由がわかりません。問題をより明確にするために、次のサンプルコードを作成しました。基本的に、誤って呼び出されないように、コピー コンストラクターとコピー代入演算子をプライベートにするクラスが定義されています。

#include <vector>
#include <cstdio>
using std::vector;

class branch 
{
public:
  int th;

private:
  branch( const branch& other );
  const branch& operator=( const branch& other );

public:

  branch() : th(0) {}

  branch( branch&& other )
  {
    printf( "called! other.th=%d\n", other.th );
  }

  const branch& operator=( branch&& other )
  {
    printf( "called! other.th=%d\n", other.th );
    return (*this);
  }

};



int main()
{
  vector<branch> v;
  branch a;
  v.push_back( std::move(a) );

  return 0;
}

このコードがコンパイルされることを期待していますが、gcc で失敗します。実際、gcc は「branch::branch(const branch&) is private」と不平を言っていますが、これは私が理解しているように、呼び出されるべきではありません。

main() の本体を次のように置き換えると、代入演算子が機能します。

branch a;
branch b;
b = a;

期待どおりにコンパイルおよび実行されます。

これは gcc の正しい動作ですか? もしそうなら、上記のコードの何が問題になっていますか? どんな提案も私にとって役に立ちます。ありがとうございました!

4

2 に答える 2

18

move コンストラクターの宣言に「noexcept」を追加してみてください。

標準を引用することはできませんが、最近のバージョンの gcc では、コピー コンストラクターをパブリックにするか、ムーブ コンストラクターを "noexcept" と宣言する必要があるようです。「noexcept」修飾子に関係なく、コピー コンストラクターをパブリックにすると、実行時に期待どおりに動作します。

于 2012-07-15T02:25:08.833 に答える
10

前の回答で示唆されたのとは異なり、gcc 4.7 はこのコードを拒否するのが間違っていました。これはgcc 4.8 で修正されています。

の完全な標準準拠の動作vector<T>::push_backは次のとおりです。

  • コピー コンストラクターのみがあり、ムーブ コンストラクターがない場合は、push_backその引数をコピーし、強力な例外安全性保証を提供します。つまり、ベクトル ストレージの再割り当てによってトリガーされた例外が原因で push_back が失敗した場合、元のベクトルは変更されずに使用可能なままになります。これは C++98 からの既知の動作であり、その後の混乱の原因でもあります。
  • noexceptの移動コンストラクターTpush_backある場合、 はその引数から移動し、強力な例外保証を提供します。ここで驚きはありません。
  • そうでない ムーブ コンストラクターがnoexceptあり、コピー コンストラクターもある場合、オブジェクトpush_backコピーし、強力な例外安全性を保証します。これは一見すると予想外です。ここにpush_back移動することはできますが、強力な例外保証を犠牲にしてのみ可能です。コードを C++98 から C++11 に移植し、型が移動可能である場合、既存のpush_back呼び出しの動作が暗黙のうちに変更されます。この落とし穴を回避し、C++98 コードとの互換性を維持するために、C++11 は低速のコピーにフォールバックします。これが gcc 4.7 の動作のすべてです。しかし、もっとあります...
  • コピー コンストラクターではなく、コピー コンストラクターではないムーブ コンストラクターがある場合 (noexceptつまり、要素を移動することはできますが、コピーすることはできません) push_back、ムーブは実行されますが、強力な例外の安全性は保証されません。これは、gcc 4.7 が間違っていた場所です。C++98 ではpush_back、移動可能だがコピー可能ではない型の s はありません。したがって、ここで強力な例外安全性を犠牲にしても、既存のコードが壊れることはありません。これが許可されている理由であり、元のコードは実際には正当な C++11 です。

次のcppreference.comを参照してpush_backください。

例外がスローされた場合、この関数は効果がありません (強力な例外保証)。

T の移動コンストラクターが noexcept ではなく、コピー コンストラクターにアクセスできない場合、vector はスロー移動コンストラクターを使用します。スローした場合、保証は免除され、効果は規定されていません。

または、C ++ 11標準のもう少し複雑な§23.3.6.5(強調は私が追加しました):

新しいサイズが古い容量より大きい場合、再割り当てが発生します。再割り当てが発生しない場合、挿入ポイントの前のすべての反復子と参照は有効なままです。T のコピー コンストラクター、ムーブ コンストラクター、代入演算子、ムーブ代入演算子、または任意の InputIterator 操作以外によって例外がスローされた場合、影響はありません。CopyInsertable 以外の T のムーブ コンストラクターによって例外がスローされた場合、その影響は規定されていません。

または、読むのが苦手な方は、Scott Meyer の Going Native 2013 トーク(0:30:20 から始まり、興味深い部分は 0:42:00 あたり) をご覧ください。

于 2013-09-17T20:00:34.737 に答える