51

一般に、標準では、移動された値にほとんど要件を課していないことを知っています。

N3485 17.6.5.15 [lib.types.movedfrom]/1:

C++ 標準ライブラリで定義されている型のオブジェクトは、(12.8) から移動できます。移動操作は、明示的に指定することも、暗黙的に生成することもできます。特に指定がない限り、そのような移動元オブジェクトは、有効であるが指定されていない状態に置かれます。

vectorこの段落から明示的に除外することについては何も見つかりません。ただし、ベクトルが空でなくなるような適切な実装を思いつくことはできません。

私が行方不明になっていることを伴う標準的なものはありますか、それともC++03 で連続したバッファとして扱うことbasic_stringに似ていますか?

4

4 に答える 4

74

私はこのパーティーに遅れて参加し、現時点で他の回答が完全に正しいとは思わないため、追加の回答を提供します。

質問:

移動元のベクトルは常に空ですか?

答え:

通常、常にではありません。

悲惨な詳細:

vector一部のタイプのように、標準定義の移動元状態はありません (たとえば、移動元の後にunique_ptr等しいと指定されています)。nullptrただし、要件vectorはそれほど多くのオプションがないようなものです。

vector答えは、のムーブ コンストラクターとムーブ代入演算子のどちらについて話しているかによって異なります。vector後者の場合、答えはのアロケータにも依存します。


vector<T, A>::vector(vector&& v)

この操作の複雑さは一定でなければなりません。vつまり、constructからリソースを盗んで、空の状態の*thisままにする以外に選択肢がないことを意味します。vこれは、アロケーターAが何であれ、型Tが何であるかに関係なく当てはまります。

したがって、移動コンストラクターの場合、移動元vectorは常に空になります。これは直接指定されていませんが、複雑さの要件から外れており、それを実装する他の方法がないという事実です。


vector<T, A>&
vector<T, A>::operator=(vector&& v)

これはかなり複雑です。3 つの主要なケースがあります。

1:

allocator_traits<A>::propagate_on_container_move_assignment::value == true

(propagate_on_container_move_assignmentに評価されtrue_typeます)

この場合、move 代入演算子は 内のすべての要素を破壊*thisし、アロケータ from を使用して容量の割り当てを解除し、アロケータを*thismove 代入してから、メモリ バッファの所有権を から に転送v*thisます。の要素の破棄を除いて*this、これは O(1) の複雑な操作です。また、通常 (すべてではありませんがほとんどの std:: アルゴリズムで)、move 割り当ての lhs は move 割り当てのempty() == true前にあります。

注: C++11 ではpropagate_on_container_move_assignmentforstd::allocatorは ですがfalse_type、これはtrue_typefor C++1y (y == 4 であることが望ましい) に変更されました。

One の場合、moved-fromvectorは常に空になります。

二:

allocator_traits<A>::propagate_on_container_move_assignment::value == false
    && get_allocator() == v.get_allocator()

(propagate_on_container_move_assignmentは に評価されfalse_type、2 つのアロケータは等しいと比較されます)

この場合、ムーブ代入演算子はケース 1 と同じように動作しますが、次の例外があります。

  1. アロケータは移動割り当てされていません。
  2. このケースとケース 3 の間の決定は実行時に行われ、ケース 3 はより多くの を必要Tとするため、ケース 2 も必要になりますが、ケース 2 は でこれらの追加の要件を実際には実行しませんT

ケース 2 では、moved-fromvectorは常に空になります。

三:

allocator_traits<A>::propagate_on_container_move_assignment::value == false
    && get_allocator() != v.get_allocator()

(propagate_on_container_move_assignmentは に評価されfalse_type、2 つのアロケータは等しくありません)

vこの場合、実装はアロケータを移動して割り当てることも、リソースを転送することもできません*this(メモリ バッファであるリソース)。この場合、移動代入演算子を効果的に実装する唯一の方法は次のとおりです。

typedef move_iterator<iterator> Ip;
assign(Ip(v.begin()), Ip(v.end()));

つまり、各個体Tを からvに移動し*thisます。は、利用可能な場合はとのassign両方を再利用できます。たとえば、実装と同じものを持っている場合は、それぞれを からに割り当てることができます。これは である必要があります。移動代入演算子が必要ないことに注意してください。コピー代入演算子でも十分です。 just は右辺値から割り当て可能でなければならないことを意味します。capacitysize*this*thissizevTv*thisTMoveAssignableMoveAssignableTMoveAssignableTT

size*thisが十分でない場合は、で newTを構築する必要があります*this。これは である必要TがありますMoveInsertable。私が考えることができる正気のアロケーターはMoveInsertable、 と同じものにMoveConstructible要約されます。これは、右辺値から構築可能であることを意味しますT( の移動コンストラクターの存在を意味するものではありませんT)。

3 の場合、moved-fromvectorは一般に空にはなりません。移動元の要素でいっぱいになる可能性があります。要素に移動コンストラクターがない場合、これはコピー割り当てと同等になる可能性があります。ただし、これを強制するものは何もありません。実装者は、必要に応じて自由に追加の作業を行って実行し、空v.clear()のままにすることができます。v私は実装がそうしていることを認識していませんし、実装がそうする動機についても認識していません。しかし、私はそれを禁止するものは何も見ません。

v.clear()David Rodríguez は、この場合、 GCC 4.8.1 が呼び出し、v空 のままであると報告しています。libc++はそうではなく、v空ではありません。どちらの実装も準拠しています。

于 2013-07-18T23:50:57.147 に答える
4

多くの状況で、move-construction と move-assignment は委譲することで実装できますswap- 特にアロケーターが関与していない場合。これにはいくつかの理由があります。

  • swapとにかく実装しなければならない
  • 記述する必要のあるコードが少ないため、開発者の効率が向上します
  • 合計で実行される操作が少なくなるため、ランタイム効率が向上します

これは移動割り当ての例です。この場合、移動先ベクトルが空でない場合、移動元ベクトルは空になりません。

auto operator=(vector&& rhs) -> vector&
{
    if (/* allocator is neither move- nor swap-aware */) {
        swap(rhs);
    } else {
        ...
    }
    return *this;
}
于 2013-07-18T18:07:45.500 に答える
0

私は他の回答にこの趣旨のコメントを残しましたが、完全に説明する前に急いで立ち去らなければなりませんでした. 移動元ベクトルの結果は常に空でなければならないか、移動代入の場合は、空または前のオブジェクトの状態 (つまりスワップ) のいずれかでなければなりません。それらを無効にしません。検討:

std::vector<int> move;
std::vector<int>::iterator it;
{
    std::vector<int> x(some_size);
    it = x.begin();
    move = std::move(x);
}
std::cout << *it;

ここでは、イテレータの無効化によって移動の実装公開されていることがわかります。このコードが正当であるという要件、具体的には反復子が有効なままであるという要件により、実装がコピーや小さなオブジェクト ストレージなどを実行できなくなります。コピーが作成された場合、optional が空になると無効になり、 がある種の SSO ベースのストレージを使用するit場合も同様です。vector基本的に、合理的に可能な唯一の実装は、ポインターを交換するか、単純に移動することです。

すべてのコンテナの要件に関する標準見積もりをご覧ください。

X u(rv)    
X u = rv    

post: u は、この構築前に rv が持っていた値と等しいものとします

a = rv

a は、この割り当ての前に rv が持っていた値と等しくなければなりません

イテレータの有効性は、コンテナの値の一部です。規格はこれを明確に直接述べているわけではありませんが、たとえば次のように見ることができます。

begin() は、コンテナー内の最初の要素を参照する反復子を返します。end() は、コンテナーの終了値を過ぎたイテレーターを返します。コンテナが空の場合、begin() == end();

メモリをスワップする代わりにソースの要素から実際に移動した実装はすべて欠陥があるため、そうでないという標準の文言は欠陥であることをお勧めします-特に、標準はこの点で実際にはあまり明確ではないためです. これらの引用は N3691 からのものです。

于 2013-07-18T18:45:45.920 に答える