同じベクターから要素をpush_backしても安全ですか?


126
vector<int> v;
v.push_back(1);
v.push_back(v[0]);

2番目のpush_backによって再割り当てが発生した場合、ベクトルの最初の整数への参照は無効になります。これは安全ではありませんか?

vector<int> v;
v.push_back(1);
v.reserve(v.size() + 1);
v.push_back(v[0]);

これは安全ですか?


4
注:現在、標準提案フォーラムで議論が行われています。その一部として、誰かがの実装例を示しpush_backました。別の投稿者は、あなたが説明したケースを適切に処理しなかったというバグを指摘しました。他の誰も、私の知る限り、これはバグではないと主張しました。それが決定的な証拠であると言っているのではなく、単なる観察です。
ベンジャミンリンドリー2013

9
申し訳ありませんが、正解についてはまだ論争があるため、どの回答を受け入れるかわかりません。
ニール・カーク

4
stackoverflow.com/a/18647445/576911の 5番目のコメントで、この質問にコメントするように求められました。私は現在言っているすべての答えを賛成することによってそうしています:はい、同じベクトルから要素をpush_backすることは安全です。
ハワードヒナン2013

2
@BenVoigt:<shrug>標準の内容に同意しない場合、または標準に同意しても、それが十分に明確であるとは思わない場合、これは常にオプションです: cplusplus.github.io/LWG/ lwg-active.html#submit_issue このオプションを自分で何度も覚えています。成功する場合もあれば、そうでない場合もあります。標準が何を言っているか、何を言うべきかについて議論したい場合、SOは効果的なフォーラムではありません。私たちの会話には規範的な意味はありません。しかし、上記のリンクをたどることにより、規範的な影響を与える可能性があります。
ハワードヒナント2013

2
@ Polaris878 push_backによってベクターがその容量に達すると、ベクターは新しい大きなバッファーを割り当て、古いデータをコピーしてから、古いバッファーを削除します。次に、新しい要素を挿入します。問題は、新しい要素が、削除されたばかりの古いバッファ内のデータへの参照であることです。push_backが削除前に値のコピーを作成しない限り、それは悪い参照になります。
Neil Kirk

回答:


31

http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-closed.html#526は、標準の潜在的な欠陥としてこの問題(またはそれに非常に類似したもの)に対処したようです

1)const参照によって取得されるパラメーターは、関数の実行中に変更できます

例:

与えられたstd :: vector v:

v.insert(v.begin()、v [2]);

v [2]は、ベクトルの要素を移動することで変更できます

提案された解決策は、これは欠陥ではないということでした:

vector :: insert(iter、value)は、標準が機能しないことを許可していないため、機能する必要があります。


17.6.4.9で許可を見つけました。「関数への引数に無効な値(関数のドメイン外の値や、使用目的に対して無効なポインターなど)がある場合、動作は未定義です。」再割り当てが発生すると、すべての反復子と要素への参照が無効化されます。つまり、関数に渡されるパラメーター参照も無効になります。
Ben Voigt 2013

4
ポイントは、実装が再割り当てを行う責任があるということです。入力が最初に定義されている場合に動作が定義されていることを保証するのは、その責任です。仕様ではpush_backがコピーを作成することを明確に指定しているため、実装では、実行時間を犠牲にして、割り当てを解除する前にすべての値をキャッシュまたはコピーする必要があります。この特定の質問では外部参照が残っていないため、イテレータと参照が無効化されているかどうかは問題ではありません。
OlivierD 2013

3
@NeilKirk私はこれが正式な答えであるべきだと思います。それはまた、本質的に同じ引数を使用してRedditの Stephan T. Lavavejによって言及されています。
TemplateRex

v.insert(v.begin(), v[2]);再割り当てをトリガーすることはできません。それで、これはどのように質問に答えますか?
ThomasMcLeod 2017

@ThomasMcLeod:はい、明らかに再割り当てをトリガーできます。新しい要素を挿入してベクターのサイズを拡張しています。
バイオレットキリン

21

はい、それは安全であり、標準ライブラリの実装はそれを行うためにフープをジャンプします。

私は実装者がこの要件を23.2 / 11に遡ると信じていますが、私はその方法を理解できず、より具体的なものも見つけられません。私が見つけることができる最高のものはこの記事です:

http://www.drdobbs.com/cpp/copying-container-elements-from-the-c-li/240155771

libc ++とlibstdc ++の実装を検査すると、それらも安全であることがわかります。


9
いくつかのバッキングはここで本当に役立ちます。
クリス

3
それは興味深いです。私はこの事件を一度も考えたことがないことを認めざるを得ませんが、確かにそれを達成することは非常に難しいようです。それはまた保持しvec.insert(vec.end(), vec.begin(), vec.end());ますか?
Matthieu M.

2
@MatthieuM。いいえ:表100には、「pre:iとjはaのイテレーターではない」と記載されています。
Sebastian Redl 2013

2
これも私の思い出ですので、私は賛成票を投じていますが、参照が必要です。
bames53

3
使用しているバージョンの23.2 / 11は、「(明示的に、または他の関数で関数を定義することにより)特に指定されていない限り、コンテナーメンバー関数を呼び出すか、コンテナーをライブラリー関数の引数として渡しても、イテレーターは無効になりません。そのコンテナ内のオブジェクトの値を変更または変更します。」?しかし、vector.push_backそれ以外の場合は指定します。「新しいサイズが古い容量より大きい場合、再割り当てが発生します。」そして(at reserve)「再割り当ては、シーケンスの要素を参照するすべての参照、ポインタ、イテレータを無効にします。」
Ben Voigt

13

この規格は、最初の例でも安全であることを保証しています。C ++ 11の引用

[sequence.reqmts]

3表100および101で、... Xはシーケンスコンテナークラスをa示し、をX含む型の要素の値を示しTます... tは、左辺値または定数右辺値を示しますX::value_type

16表101 ...

a.push_back(t) 戻り値の型 void 動作セマンティクスt. 必要な Tもののコピーを追加しますCopyInsertable入れXます。 コンテナ basic_stringdequelistvector

したがって、それは厳密なことではありませんが、実装では、を実行するときに参照が無効にならないことを保証する必要がありpush_backます。


7
これがこれが安全であることをどのように保証するかはわかりません。
jrok

4
@Angew:完全に無効tになります。唯一の問題は、コピーを作成する前か後かです。あなたの最後の文は確かに間違っています。
Ben Voigt 2013

4
@BenVoigt tリストされた前提条件を満たしているため、説明されている動作が保証されます。実装は、前提条件を無効にしてから、それを指定どおりに動作しない言い訳として使用することはできません。
bames53 2013

8
@BenVoigtクライアントは、呼び出し全体で前提条件を維持する義務はありません。コールの開始時に確実に満たされるようにするためだけです。
bames53

6
@BenVoigtそれは良い点ですが、渡されたファンクタfor_eachはイテレータを無効にしないことが要求されると私は信じています。のリファレンスを思い付くことはできませんがfor_each、「opおよびbinary_opはイテレータまたはサブ範囲を無効にしない」などの一部のアルゴリズムテキストを参照しています。
bames53 2013

7

最初の例が安全であることは明らかではありません。なぜなら、の最も簡単な実装はpush_back、必要に応じて最初にベクトルを再割り当てしてから、参照をコピーすることです。

しかし、少なくともVisual Studio 2010では安全であると思われます。その実装はpush_back、ベクター内の要素をプッシュバックした場合に特別な処理を行います。コードは次のように構成されています。

void push_back(const _Ty& _Val)
    {   // insert element at end
    if (_Inside(_STD addressof(_Val)))
        {   // push back an element
                    ...
        }
    else
        {   // push back a non-element
                    ...
        }
    }

8
安全のために仕様でこれが必要かどうかを知りたいのですが。
Nawaz 2013

1
規格によれば、安全である必要はありません。ただし、安全な方法で実装することは可能です。
Ben Voigt

2
@BenVoigt 安全である必要があると思います(私の回答を参照)。
Angewは

2
@BenVoigt参照を渡すとき、それは有効です。
Angewは

4
@Angew:それは十分ではありません。呼び出しの間有効な参照を渡す必要がありますが、これはそうではありません。
Ben Voigt 2013

3

これは標準からの保証ではありませんが、別のデータポイントとしてv.push_back(v[0])LLVMのlibc ++にとって安全です

std::vector::push_back__push_back_slow_pathメモリを再割り当てする必要がある場合のlibc ++の呼び出し:

void __push_back_slow_path(_Up& __x) {
  allocator_type& __a = this->__alloc();
  __split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1), 
                                                  size(), 
                                                  __a);
  // Note that we construct a copy of __x before deallocating
  // the existing storage or moving existing elements.
  __alloc_traits::construct(__a, 
                            _VSTD::__to_raw_pointer(__v.__end_), 
                            _VSTD::forward<_Up>(__x));
  __v.__end_++;
  // Moving existing elements happens here:
  __swap_out_circular_buffer(__v);
  // When __v goes out of scope, __x will be invalid.
}

コピーは、既存のストレージの割り当てを解除する前だけでなく、既存の要素から移動する前に行う必要があります。既存の要素の移動はで行われると思い__swap_out_circular_bufferます。その場合、この実装は確かに安全です。
Ben Voigt 2013

@BenVoigt:良い点、そして動きは内部で発生することは確かに正しい__swap_out_circular_buffer。(私はそれを注記するためにいくつかのコメントを追加しました。)
Nate Kohl

1

最初のバージョンは間違いなく安全ではありません:

標準ライブラリコンテナーまたは文字列メンバー関数を呼び出して取得したイテレーターの操作は、基になるコンテナーにアクセスできますが、変更することはできません。[注:特に、イテレーターを無効にするコンテナー操作は、そのコンテナーに関連付けられているイテレーターの操作と競合します。—エンドノート]

セクション17.6.5.9から


これは、データレースに関するセクションであり、通常はスレッド化と関連して考えられますが、実際の定義には「前に起こる」関係が含まpush_backれます。ここで再生します。つまり、参照の無効化は、新しいテール要素のコピー構築に関して順序付けられたものとして定義されていないようです。


1
これはルールではなくノートであることを理解する必要があります。したがって、これは前述のルールの結果を説明するものであり、その結果は参照でも同じです。
Ben Voigt 2013

5
の結果はv[0]イテレータではなく、同様にpush_back()イテレータを取りません。したがって、言語弁護士の観点からは、あなたの主張は無効です。ごめんなさい。ほとんどのイテレータはポインタであり、イテレータを無効にするポイントは参照の場合とほとんど同じですが、引用する標準の部分は、現在の状況とは無関係です。
cmaster-2013年

-1。それは完全に無関係な引用であり、とにかく答えません。委員会x.push_back(x[0])は安全だと言います。
Nawaz 2013

0

完全に安全です。

2番目の例では、

v.reserve(v.size() + 1);

ベクトルがそのサイズを超えた場合、それはを意味するため、これは必要ありませんreserve

ベクターはあなたではなく、このようなことを担当します。


-1

push_backは参照ではなく値をコピーするため、どちらも安全です。ポインターを格納している場合でも、ベクターに関する限りは安全ですが、ベクターの2つの要素が同じデータを指すことを知っているだけです。

セクション23.2.1一般的なコンテナ要件

16
  • a.push_back(t)tのコピーを追加します。必要:TはXへのCopyInsertableでなければならない。
  • a.push_back(rv)rvのコピーを追加します。必要:TはXへのMoveInsertableです。

したがって、push_backの実装では、のコピー v[0]が挿入されるようにする必要があります。反例として、コピーする前に再割り当てする実装を想定するv[0]と、コピーが確実に追加されず、仕様に違反することになります。


2
push_backただし、ベクトルのサイズ変更され、単純な実装では、コピーが行われる前に参照が無効になります。したがって、標準からの引用でこれを裏付けることができない限り、私はそれを間違っていると考えます。
Konrad Rudolph

4
「これ」とは、最初の例ですか、2番目の例ですか?push_back値をベクトルにコピーします。しかし、(私が見る限り)再割り当て後に発生する可能性があります。その時点で、コピー元の参照は無効になります。
マイクシーモア

1
push_back参照により引数を受け取ります
bames53 2013

1
@OlivierD:(1)新しいスペースを割り当てる(2)新しい要素をコピーする(3)既存の要素を移動して構築する(4)移動した要素を破棄する(5)古いストレージを解放する-その順序で-最初のバージョンを機能させるため。
Ben Voigt 2013

1
@BenVoigtとにかく、コンテナーがそのプロパティを完全に無視する場合、コンテナーが型をCopyInsertableにする必要があるのはなぜですか?
OlivierD 2013

-2

23.3.6.5/1から: Causes reallocation if the new size is greater than the old capacity. If no reallocation happens, all the iterators and references before the insertion point remain valid.

最後に挿入しているので、ベクターのサイズが変更されない場合、参照は無効になりません。そのため、ベクターの場合は機能するcapacity() > size()ことが保証され、そうでない場合は未定義の動作であることが保証されます。


仕様では、どちらの場合でも動作することが実際に保証されていると思います。参考までに待っています。
bames53

問題には、イテレータまたはイテレータの安全性についての言及はありません。
OlivierD 2013

3
@OlivierDイテレータ部分はここでは不要ですreferences。引用の部分に興味があります。
マークB

2
安全であることが実際に保証されています(私の答え、のセマンティクスを参照してくださいpush_back)。
Angewは、2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.