13

考えられる定義は次のstd::swapとおりです。

template<class T>
void swap(T& a, T& b) {
  T tmp(std::move(a));
  a = std::move(b);
  b = std::move(tmp);
}

私は信じている

  1. std::swap(v,v)効果がないことが保証されており、
  2. std::swap上記のように実装できます。

次の引用は、これらの信念が矛盾していることを暗示しているように私には思えます。

17.6.4.9関数の引数[res.on.arguments]

1以下のそれぞれは、特に明記されていない限り、C++標準ライブラリで定義されている関数のすべての引数に適用されます。

..。

  • 関数の引数が右辺値参照パラメーターにバインドする場合、実装は、このパラメーターがこの引数への一意の参照であると想定する場合があります。[注:パラメーターがT &&形式のジェネリックパラメーターであり、タイプAの左辺値がバインドされている場合、引数は左辺値参照(14.8.2.1)にバインドされるため、前の文ではカバーされません。— end note] [注:プログラムが左辺値をライブラリ関数に渡すときに左辺値をx値にキャストする場合(たとえば、引数move(x)を使用して関数を呼び出すことにより)、プログラムはその関数にその左辺値を処理するように効果的に要求します。一時的なものとして。実装は、引数が左辺値である場合に必要になる可能性のあるエイリアシングチェックを自由に最適化できます。—文末脚注]

見積もりを提供してくれたHoward Hinnantに感謝します)

標準vテンプレートライブラリから取得したいくつかの移動可能なタイプのオブジェクトであり、呼び出しを検討しますstd::swap(v, v)。上記の行a = std::move(b);では、その内部のケースでT::operator=(T&& t)あるthis == &bため、パラメーターは一意の参照ではありません。これは上記の要件に違反しているため、a = std::move(b)から呼び出されたときに行は未定義の動作を呼び出しますstd::swap(v, v)

ここでの説明は何ですか?

4

5 に答える 5

7

[res.on.arguments] は、クライアントが std::lib を使用する方法についてのステートメントです。クライアントが xvalue を std::lib 関数に送信する場合、クライアントは、xvalue が実際には prvalue であると偽って、std::lib がそれを利用することを期待する必要があります。

ただし、クライアントが std::swap(x, x) を呼び出すとき、クライアントは xvalue を std::lib 関数に送信していません。代わりにそうしているのは実装です。そのため、 std::swap(x, x) を機能させる責任は実装にあります。

そうは言っても、std は実装者に保証を与えています: X は満たす必要がありMoveAssignableます。移動元の状態であっても、クライアントは X が MoveAssignable であることを確認する必要があります。さらに、 の実装はstd::swap、クラッシュしない限り、XIe の未定義の動作でない限り、self-move-assignment が何をするかはあまり気にしません。

a = std::move(b);

&a == &b の場合、この割り当てのソースとターゲットの両方に未指定の (移動元の) 値があります。これはノーオペレーションの場合もあれば、別のことを行う場合もあります。クラッシュしない限り、std::swap は正しく動作します。これは、次の行にあるためです。

b = std::move(tmp);

前の行から入力された値は、aから新しい値が与えられますtmptmpの元の値を持っていますa。そのため、多くの CPU サイクルを消費swap(a, a)するだけでなく、ノーオペレーションです。

アップデート

最新の作業草案である N4618MoveAssignableは、要件で次の式を明確に述べるように修正されました。

t = rv

(ここでrvは右辺値です)、とが同じオブジェクトを参照しない場合にのみ、代入前tの同等の値である必要があります。それに関係なく、割り当て後の の状態は指定されていません。さらに明確にするための追加のメモがあります。rvtrvrv

rv同じオブジェクトtを参照するかどうかに関係なく、それを使用しているライブラリ コンポーネントの要件を満たす必要があります。rv

于 2012-10-29T20:50:54.910 に答える
2

次に式a = std::move(b);が実行され、オブジェクトはすでに空であり、破壊のみが明確に定義されている状態です。左側と右側のオブジェクトはすでに空であるため、これは事実上何もしません。移動後のオブジェクトの状態は不明ですが、破壊可能です。次のステートメントは、コンテンツを から戻しtmp、オブジェクトを既知の状態に戻します。

于 2012-10-29T20:39:10.540 に答える
2

私はあなたの分析に同意します。実際、libstdc++ デバッグ モードには、標準コンテナーの自己スワップで起動するアサーションがあります。

#include <vector>
#include <utility>

struct S {
  std::vector<int> v;
};

int main()
{
  S s;
  std::swap(s, s);
}

ラッパー型Sが必要なのは、スワッピング ベクトルが呼び出す特殊化を直接使用するため、ジェネリックをvector::swap()使用せず、ジェネリックを使用し、C++11 としてコンパイルすると、中止されるベクトルメンバー:std::swapS

/home/toor/gcc/4.8.2/include/c++/4.8.2/debug/vector:159:error: PST.

Objects involved in the operation:
sequence "this" @ 0x0x7fffe8fecc00 {
  type = NSt7__debug6vectorIiSaIiEEE;
}
Aborted (core dumped)

(「PST」が何を意味するのかわかりません!テストしたインストールに何か問題があると思います。)

ここでのGCCの動作は準拠していると思います。標準では、実装は自己移動割り当てが発生しないと想定できるため、有効なプログラムでアサーションが失敗することは決してないからです。

ただし、これが機能する必要があるというハワードに同意します (そして、あまり問題なく機能させることができます - libstdc++ では、デバッグ モード アサーションを削除するだけで済みます!)。そのため、標準を修正して例外を作成する必要があります自己移動、または少なくとも自己スワップ。私はしばらくの間、この問題について論文を書くことを約束してきましたが、まだ書いていません。

ここに彼の回答を書いて以来、ハワードは標準の現在の文言に問題があることに同意し、libstdc++ が失敗するアサーションを行うことを禁止するために修正する必要があると信じています。

于 2015-03-16T00:37:10.850 に答える
0

は右辺値参照ではなく左辺値参照を取るように定義されてstd::swapいるため、有効な定義ではないと思います (20.2.2 [utility.swap])std::swap

于 2012-10-29T20:35:10.373 に答える