42

std::stringの (実際にはstd::basic_stringの) 移動代入演算子が であることに気付きましたnoexcept。それは私には理にかなっています。std::vectorしかしその後、標準のコンテナ ( 、 、std::dequestd::listなどstd::map) のどれも移動代入演算子を宣言していないことに気付きましたnoexcept。それは私にはあまり意味がありません。std::vectorたとえば、Aは通常 3 つのポインターとして実装され、ポインターは例外をスローすることなく確実に移動代入できます。次に、コンテナのアロケータの移動に問題があるのではないかと考えましたがstd::string、 にもアロケータがあるため、それが問題である場合は、std::string.

では、なぜstd::stringの移動代入演算子noexceptは であるのに、標準コンテナーの移動代入演算子はそうではないのでしょうか?

4

3 に答える 3

26

標準の欠陥を見ていると思います。noexceptムーブ代入演算子に適用する場合、仕様がやや複雑になります。そして、私たちが話しているのbasic_stringvector.

[container.requirements.general]/p7 に基づいて、コンテナー移動割り当てオペレーターが行うべきことの私の英語の翻訳は次のとおりです。

C& operator=(C&& c)

alloc_traits::propagate_on_container_move_assignment::valueが の場合true、 リソースをダンプし、 move がアロケータを割り当て、 からリソースを転送しcます。

alloc_traits::propagate_on_container_move_assignment::valuefalse との場合 、 はget_allocator() == c.get_allocator()リソースをダンプし、 からリソースを転送しcます。

alloc_traits::propagate_on_container_move_assignment::valuefalse との場合 get_allocator() != c.get_allocator()、move はそれぞれに を割り当てますc[i]

ノート:

  1. alloc_traitsを指しallocator_traits<allocator_type>ます。

  2. 現在のリソースの割り当てを解除し、ソースからリソースを盗むだけであるためalloc_traits::propagate_on_container_move_assignment::valuetrue移動割り当て演算子を指定できるのはいつですか。noexceptまた、この場合、アロケーターにも移動が割り当てられている必要があり、その移動の割り当てはnoexcept、コンテナーの移動の割り当てがnoexcept.

  3. alloc_traits::propagate_on_container_move_assignment::valuefalse、2 つのアロケータが等しい場合、#2 と同じことを行います。ただし、アロケーターが実行時まで等しいかどうかはわからないためnoexcept、この可能性に基づくことはできません。

  4. alloc_traits::propagate_on_container_move_assignment::valuefalse、2 つのアロケータが等しくない場合、個々の要素を移動して割り当てる必要があります。これには、ターゲットへの容量またはノードの追加が含まれる場合があるため、本質的にnoexcept(false).

要約すると:

C& operator=(C&& c)
        noexcept(
             alloc_traits::propagate_on_container_move_assignment::value &&
             is_nothrow_move_assignable<allocator_type>::value);

また、上記の仕様には への依存が見られないため、 C++11 が別の方法で指定しているにもかかわらずC::value_type、 にも同様に適用されるはずです。std::basic_string

アップデート

以下のコメントで、コロンボは物事が常に徐々に変化していることを正しく指摘しています。上記の私のコメントは C++11 に関連しています。

ドラフト C++17 (現時点では安定しているように見えます) では、状況が若干変更されています。

  1. が の場合、仕様でalloc_traits::propagate_on_container_move_assignment::valueは、例外をスローしないtrueように の移動割り当てが必要になりましたallocator_type(17.6.3.5 [allocator.requirements]/p4)。したがって、もうチェックする必要はありませんis_nothrow_move_assignable<allocator_type>::value

  2. alloc_traits::is_always_equal追加されました。これが真である場合、コンパイル時に、リソースを転送できるため、上記のポイント 3 をスローできないと判断できます。

したがって、noexceptコンテナーの新しい仕様は次のようになります。

C& operator=(C&& c)
        noexcept(
             alloc_traits::propagate_on_container_move_assignment{} ||
             alloc_traits::is_always_equal{});

そして、 for std::allocator<T>alloc_traits::propagate_on_container_move_assignment{}alloc_traits::is_always_equal{}は両方とも真です。

また、現在 C++17 ドラフトでは、vectorstring移動代入の両方に、まさにこのnoexcept仕様が適用されています。noexceptただし、他のコンテナにはこの仕様のバリエーションがあります。

この問題が気になる場合に最も安全な方法は、気になるコンテナーの明示的な特殊化をテストすることです。container<T>ここで、VS、libstdc ++、およびlibc ++に対して正確にそれを行いました:

http://howardhinnant.github.io/container_summary.html

この調査は約 1 年前のものですが、私の知る限り、まだ有効です。

于 2012-09-08T22:15:47.323 に答える