私が知る限り、_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
_の方が遅いことに注意する必要があるようです。 。
したがって、RandomAccessIterator
sを使用するコンテナ用にテンプレートを特化して、代わりに_back_inserter
_を使用することができます。それは理にかなっていますか?ありがとう。
std::back_inserter
_は、Container::Push_back()
を使用する_std::back_insert_iterator
_を返します。std::inserter
_は、Container::insert()
を使用する_std::insert_iterator
_を返します。リストの場合、_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
_の場合は少し異なります。再割り当てが必要かどうかをチェックバックし、正しい場所に値を配置するだけではない場合。
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)
}
_
簡単な答えは、_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
_は常に最後に挿入されるため、ペナルティもありません。