web-dev-qa-db-ja.com

同じベクトルの要素をPush_backしても安全ですか?

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]);

これで安全になりますか?

125
Neil Kirk

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)は、標準が機能しない許可を与えていないため、機能するために必要です。

31
Nate Kohl

はい、それは安全です、そして、標準ライブラリ実装はそれをするために輪を飛び越えます。

実装者はこの要件をなんとか23.2/11にまでさかのぼると思いますが、どうすればよいかわかりませんし、より具体的なものも見つかりません。私が見つけることができる最高のこの記事です:

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

Libc ++およびlibstdc ++の実装を調べると、これらも安全であることがわかります。

21
Sebastian Redl

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

[sequence.reqmts]

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

16表101 ...

a.Push_back(t)戻り値のタイプvoid操作上のセマンティクス _t._Requires:TCopyInsertable into Xになります。 コンテナ _basic_string_、dequelistvector

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

13
Angew

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
                    ...
        }
    }
7
Johan Råde

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

libc ++のstd::vector::Push_back は、メモリの再割り当てが必要なときに__Push_back_slow_pathを呼び出します。

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.
}
3
Nate Kohl

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

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

セクション17.6.5.9から


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

1
Ben Voigt

完全に安全です。

2番目の例では

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

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

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

0
Zaffy