web-dev-qa-db-ja.com

std :: inserterに対するstd :: back_inserterの利点は何ですか?

私が知る限り、_std::back_inserter_がSTLアルゴリズムで機能する場合は、代わりに.end()で構築された_std::inserter_を渡すことができます。

_std::copy(l.begin(), l.end(), std::back_inserter(dest_list));
std::copy(l.begin(), l.end(), std::inserter(dest_list, dest_list.end()));
_

また、_back_inserter_とは異なり、inserterがすべてのSTLコンテナで機能することを伝えることができる限り!!ここに来る前に、_std::vector_、_std::list_、_std::map_、_std::unordered_map_を試してみました。

多分、それは_Push_back_がinsert(.end())よりもいくつかの構造に対して高速である可能性があるからだと思いましたが、よくわかりません...

_std::list_には当てはまらないようです(意味があります):

_// Copying 10,000,000 element-list with std::copy. Did it twice w/ switched order just in case that matters.
Profiling complete (884.666 millis total run-time): inserter(.end())
Profiling complete (643.798 millis total run-time): back_inserter
Profiling complete (644.060 millis total run-time): back_inserter
Profiling complete (623.151 millis total run-time): inserter(.end())
_

しかし、それは_std::vector_に対してわずかに機能しますが、理由は本当にわかりませんか?

_// Copying 10,000,000 element-vector with std::copy.
Profiling complete (985.754 millis total run-time): inserter(.end())
Profiling complete (746.819 millis total run-time): back_inserter
Profiling complete (745.476 millis total run-time): back_inserter
Profiling complete (739.774 millis total run-time): inserter(.end())
_

ベクトルでは、イテレータの位置を特定し、そこにarr [count ++]だけでなく要素を配置するオーバーヘッドがわずかに多いと思います。たぶんそれ?

それでも、それが主な理由ですか?

私のフォローアップの質問は、「テンプレート化された関数に対してstd::inserter(container, container.end())を書いて、それが(ほぼ)任意のSTLコンテナで動作することを期待してもいいですか?」


標準のコンパイラに移行した後、数値を更新しました。コンパイラの詳細は次のとおりです。
gccバージョン4.8.2(Ubuntu 4.8.2-19ubuntu1)
ターゲット:x86_64-linux-gnu

私のビルドコマンド:

_g++ -O0 -std=c++11 algo_test.cc
_

この質問は私の質問の後半を尋ねます 、つまり、「std::inserter(container, container.end())を使用するテンプレート化された関数を記述し、ほぼすべてのコンテナで機能することを期待できますか?」

答えは「はい、_std::forward_list_を除くすべてのコンテナに対して」でした。しかし、以下のコメントと ser274625 の回答での議論に基づいて、これは_std::vector_を使用するよりも_std::back_inserter_の方が遅いことに注意する必要があるようです。 。

したがって、RandomAccessIteratorsを使用するコンテナ用にテンプレートを特化して、代わりに_back_inserter_を使用することができます。それは理にかなっていますか?ありがとう。

46
NHDaly

反復子のタイプ

  • _std::back_inserter_は、Container::Push_back()を使用する_std::back_insert_iterator_を返します。
  • _std::inserter_は、Container::insert()を使用する_std::insert_iterator_を返します。

std :: list

リストの場合、_std::list::Push_back_は_std::list::insert_とほぼ同じです。唯一の違いは、挿入は挿入された要素に反復子を返すことです。

bits/stl_list.h

_void Push_back(const value_type& __x)
  { this->_M_insert(end(), __x); }
void _M_insert(iterator __position, const value_type& __x)
  {
    _Node* __tmp = _M_create_node(__x);
    __tmp->_M_hook(__position._M_node);
  }
_

bits/list.tcc

_template<typename _Tp, typename _Alloc> typename list<_Tp, _Alloc>::iterator
list<_Tp, _Alloc>::insert(iterator __position, const value_type& __x)
  {
  _Node* __tmp = _M_create_node(__x);
  __tmp->_M_hook(__position._M_node);
  return iterator(__tmp);
  }
_

std :: vector

_std::vector_の場合は少し異なります。再割り当てが必要かどうかをチェックバックし、正しい場所に値を配置するだけではない場合。

bits/stl_vector.h

_void Push_back(const value_type& __x)
  {
  if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage)
    {
    _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish, __x);
    ++this->_M_impl._M_finish;
    }
  else
    _M_insert_aux(end(), __x);
  }
_

ただし、_std::vector::insert_には3つの追加処理があり、パフォーマンスに影響します。 bits/vector.tcc

_template<typename _Tp, typename _Alloc> typename vector<_Tp, _Alloc>::iterator
vector<_Tp, _Alloc>::insert(iterator __position, const value_type& __x)
  {
  const size_type __n = __position - begin(); //(1)
  if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage
  && __position == end()) //(2)
    {
    _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish, __x);
    ++this->_M_impl._M_finish;
    }
  else
    {
    _M_insert_aux(__position, __x);
    }
  return iterator(this->_M_impl._M_start + __n); //(3)
  }
_
54
user2746253

簡単な答えは、_std::insert_iterator_を使用すると、コンテナ内の任意の位置に挿入できることです。

_//insert at index 2
auto it = std::inserter(v, v.begin() + 2);
*it = 4;
_

これを実現するには、上記の例では、std :: vectorはインデックス2の後に既存の要素を1つ下に移動する必要があります。下に移動するものが他にないため、最後に挿入しない限り、これはO(n)操作です。ただし、O(1) perfペナルティを引き起こす関連チェックを行う必要があります。リンクリストの場合、O(1) timeの任意の場所に挿入できるため、ペナルティはありません。 _back_inserter_は常に最後に挿入されるため、ペナルティもありません。

0
Shital Shah