web-dev-qa-db-ja.com

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

私は一般的に、標準が移動された値にいくつかの要件を課していることを知っています:

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

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

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

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

40
Billy ONeal

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

質問:

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

回答:

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

残酷な詳細:

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つの主要なケースがあります:

1:

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

propagate_on_container_move_assignmenttrue_typeに評価されます)

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

注:C++ 11では、propagate_on_container_move_assignmentstd::allocatorfalse_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_assignmentfalse_typeと評価され、2つのアロケーターは同等に比較されます)

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

  1. アロケータは移動割り当てされません。
  2. このケースとケース3の間の決定は実行時に行われ、ケース3はより多くの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_assignmentfalse_typeと評価され、2つのアロケーターは等しく比較されません)

この場合、実装はアロケータをムーブ代入することも、vから*thisにリソースを転送することもできません(リソースはメモリバッファです)。この場合、ムーブ代入演算子を実装する唯一の方法は、効果的に次のことを行うことです。

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

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

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

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

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

59
Howard Hinnant

一般的な場合は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;
}
3
nosid

私は他の答えにこの効果についてコメントを残しましたが、完全に説明する前に急いでやらなければなりませんでした。ムーブ元ベクトルの結果は常に空である必要があります。または、ムーブ代入の場合は、空であるか、前のオブジェクトの状態(つまり、スワップ)である必要があります。そうしないと、イテレータの無効化ルールを満たすことができません。それらを無効にしません。考えてみましょう:

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からのものです。

0
Puppy