私は一般的に、標準が移動された値にいくつかの要件を課していることを知っています:
N3485 17.6.5.15 [lib.types.movedfrom]/1:
C++標準ライブラリで定義されている型のオブジェクトは、(12.8)から移動できます。移動操作は、明示的に指定することも、暗黙的に生成することもできます。特に指定のない限り、そのような移動元オブジェクトは、有効であるが指定されていない状態に置かれるものとします。
この段落から明示的に除外しているvector
については何も見つかりません。ただし、ベクトルが空にならないような適切な実装を思い付くことができません。
私が行方不明になっているこれを伴ういくつかの標準的なものがありますか、またはこれは 治療basic_string
C++ 03の連続バッファとして ?
私はこのパーティーに遅れて来ますが、現時点で他の答えが完全に正しいとは思わないので、追加の答えを提供します。
質問:
移動元のベクトルは常に空ですか?
回答:
通常、しかし、常にではありません。
残酷な詳細:
vector
には、一部のタイプのように標準で定義された移動元の状態がありません(たとえば、unique_ptr
は、移動後にnullptr
と等しくなるように指定されます)。ただし、vector
の要件は、オプションが多すぎないようにするためのものです。
答えは、vector
のムーブコンストラクターとムーブ代入演算子のどちらについて話しているかによって異なります。後者の場合、答えはvector
のアロケーターにも依存します。
vector<T, A>::vector(vector&& v)
この操作は常に複雑でなければなりません。つまり、v
からリソースを盗んで*this
を構築し、v
を空の状態のままにする以外に選択肢はありません。これは、アロケータA
が何であるか、またはタイプT
が何であるかに関係なく当てはまります。
したがって、moveコンストラクターの場合、はい、moved-from vector
は常に空になります。これは直接指定されていませんが、複雑さの要件と、それを実装する他の方法がないという事実から外れています。
vector<T, A>&
vector<T, A>::operator=(vector&& v)
これはかなり複雑です。 3つの主要なケースがあります:
allocator_traits<A>::propagate_on_container_move_assignment::value == true
(propagate_on_container_move_assignment
はtrue_type
に評価されます)
この場合、ムーブ代入演算子は*this
内のすべての要素を破棄し、*this
からアロケーターを使用して容量の割り当てを解除し、アロケーターを移動割り当てしてから、メモリバッファーの所有権をv
から譲渡します。 *this
へ。 *this
の要素の破棄を除いて、これはO(1)複雑度演算です。たとえば、すべてではありませんがほとんどのstd ::アルゴリズムで)、のlhsムーブ代入には、ムーブ代入の前にempty() == true
があります。
注:C++ 11では、propagate_on_container_move_assignment
のstd::allocator
はfalse_type
ですが、C++ 1yの場合はtrue_type
に変更されています(y == 4を希望します) 。
ケース1の場合、移動元のvector
は常に空になります。
allocator_traits<A>::propagate_on_container_move_assignment::value == false
&& get_allocator() == v.get_allocator()
(propagate_on_container_move_assignment
はfalse_type
と評価され、2つのアロケーターは同等に比較されます)
この場合、ムーブ代入演算子は、次の例外を除いて、ケース1と同じように動作します。
T
を必要とするため、ケース2は実際にはT
。ケース2の場合、moved-from vector
は常に空になります。
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
は、可能であれば*this
でcapacity
とsize
の両方を再利用できます。たとえば、*this
がsize
と同じv
を持っている場合、実装は各T
をv
から*this
に移動できます。これには、T
がMoveAssignable
である必要があります。 MoveAssignable
は、ムーブ代入演算子を持つためにT
を必要としないことに注意してください。コピー割り当て演算子でも十分です。 MoveAssignable
は、T
が右辺値T
から割り当て可能でなければならないことを意味します。
*this
のsize
が十分でない場合は、新しいT
を*this
に作成する必要があります。これには、T
がMoveInsertable
である必要があります。私が考えることができる正気のアロケーターの場合、MoveInsertable
はMoveConstructible
と同じものに要約されます。これは、右辺値T
から構築可能であることを意味します(移動の存在を意味するものではありません)。 T
のコンストラクター)。
ケース3の場合、移動元のvector
は通常空ではありません。移動元の要素でいっぱいになる可能性があります。要素に移動コンストラクターがない場合、これはコピー割り当てと同等である可能性があります。ただし、これを義務付けるものはありません。実装者は、必要に応じて追加の作業を自由に実行し、v.clear()
を実行して、v
を空のままにします。私は、そうする実装を認識していません。また、実装がそうする動機も認識していません。しかし、私はそれを禁じるものは何も見ていません。
DavidRodríguezは、この場合、GCC4.8.1がv.clear()
を呼び出し、v
を空のままにしたと報告しています。 libc ++ はそうではなく、v
は空ではありません。どちらの実装も準拠しています。
一般的な場合はsane実装ではないかもしれませんが、moveコンストラクター/割り当ての有効な実装は、ソースからデータをコピーし、ソースをそのままにしておくことです。さらに、割り当ての場合、moveはswapとして実装でき、moved-fromコンテナーにはmoved-toコンテナーの古い値が含まれる場合があります。
私たちのように多形アロケーターを使用する場合、コピーとしての移動の実装は実際に発生する可能性があり、アロケーターはオブジェクトのvalueの一部とは見なされません(したがって、割り当てによって実際のアロケーターが変更されることはありません中古)。このコンテキストでは、移動操作は、ソースと宛先の両方が同じアロケーターを使用しているかどうかを検出できます。同じアロケータを使用している場合、移動操作はソースからデータを移動するだけです。異なるアロケータを使用する場合、宛先はソースコンテナをコピーする必要があります。
多くの場合、特にアロケーターが関与していない場合は、swap
に委任することでmove-constructionとmove-assignmentを実装できます。それを行う理由はいくつかあります。
swap
を実装する必要がありますムーブ代入の例を次に示します。この場合、移動先のベクトルが空でなければ、移動元のベクトルは空になりません。
auto operator=(vector&& rhs) -> vector&
{
if (/* allocator is neither move- nor swap-aware */) {
swap(rhs);
} else {
...
}
return *this;
}
私は他の答えにこの効果についてコメントを残しましたが、完全に説明する前に急いでやらなければなりませんでした。ムーブ元ベクトルの結果は常に空である必要があります。または、ムーブ代入の場合は、空であるか、前のオブジェクトの状態(つまり、スワップ)である必要があります。そうしないと、イテレータの無効化ルールを満たすことができません。それらを無効にしません。考えてみましょう:
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;
ここで、イテレータの無効化doesが移動の実装を公開していることがわかります。このコードが合法であるという要件、特にイテレータが有効なままであるという要件は、実装がコピー、またはスモールオブジェクトストレージまたは同様のものを実行することを防ぎます。コピーが作成された場合、オプションが空になるとit
は無効になり、vector
が何らかのSSOベースのストレージを使用している場合も同様です。基本的に、考えられる唯一の合理的な実装は、ポインターを交換するか、単にポインターを移動することです。
allコンテナの要件に関する標準見積もりをご覧ください。
X u(rv)
X u = rv
post:uは、この構築の前にrvが持っていた値と等しくなければなりません
a = rv
aは、この割り当ての前にrvが持っていた値と等しくなければなりません。
イテレータの有効性は、コンテナのvalueの一部です。規格はこれを直接明確に述べていませんが、たとえば、
begin()は、コンテナ内の最初の要素を参照するイテレータを返します。 end()は、コンテナの終了後の値であるイテレータを返します。コンテナが空の場合、begin()== end();
メモリを交換する代わりにソースの要素から実際に移動した実装は欠陥があるので、そうでないことを言っている標準の文言は欠陥であると私は提案します-特にこの点で標準は実際にはあまり明確ではないためです。これらの引用はN3691からのものです。