Autant que je sache, n'importe où std::back_inserter
fonctionne dans un algorithme STL, vous pouvez passer un std::inserter
construit avec .end()
à la place:
std::copy(l.begin(), l.end(), std::back_inserter(dest_list));
std::copy(l.begin(), l.end(), std::inserter(dest_list, dest_list.end()));
ET, contrairement à back_inserter
, autant que je sache, inserter
fonctionne pour TOUT conteneur STL !! Je l'ai essayé avec succès pour std::vector
, std::list
, std::map
, std::unordered_map
avant de venir ici surpris.
Je pensais que c'était peut-être parce que Push_back
pourrait être plus rapide pour certaines structures que insert(.end())
, mais je ne suis pas sûr ...
Cela ne semble pas être le cas pour std::list
(est logique):
// 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())
Mais cela fait un peu pour std::vector
, bien que je ne sois pas vraiment sûr pourquoi?
// 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())
Je suppose que dans un vecteur, il y a un peu plus de frais généraux pour savoir où se trouve l'itérateur, puis pour y placer un élément par rapport à just arr [count ++]. C'est peut-être ça?
Mais encore, est-ce la raison principale?
Ma question suivante, je suppose, est la suivante: "Peut-on écrire std::inserter(container, container.end())
pour une fonction basée sur un modèle et s’attendre à ce que cela fonctionne pour (presque) tous les conteneurs STL?"
J'ai mis à jour les nombres après avoir migré vers un compilateur standard. Voici les détails de mon compilateur:
gcc version 4.8.2 (Ubuntu 4.8.2-19ubuntu1)
Cible: x86_64-linux-gnu
Ma commande de construction:
g++ -O0 -std=c++11 algo_test.cc
Je pense cette question pose la seconde partie de ma question , à savoir: "Puis-je écrire une fonction basée sur un modèle qui utilise std::inserter(container, container.end())
et m'attends à ce qu'elle fonctionne pour presque tous les conteneurs?"
La réponse a été "Oui, pour chaque conteneur à l'exception de std::forward_list
." Mais si on se base sur la discussion dans les commentaires ci-dessous et dans la réponse de user2746253 , il semble que je devrais être conscient du fait que cela serait plus lent pour std::vector
que d'utiliser std::back_inserter
...
Par conséquent, je souhaiterais peut-être spécialiser mon modèle pour les conteneurs utilisant RandomAccessIterator
s pour utiliser back_inserter
à la place. Cela a-t-il du sens? Merci.
std::back_inserter
renvoie std::back_insert_iterator
qui utilise Container::Push_back()
.std::inserter
renvoie std::insert_iterator
qui utilise Container::insert()
.Pour les listes, std::list::Push_back
est presque identique à std::list::insert
. La seule différence est que insert renvoie l'itérateur à l'élément inséré.
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);
}
Cela semble un peu différent pour std::vector
. Repousser les vérifications si la réaffectation est nécessaire, et sinon ne place pas la valeur au bon endroit.
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);
}
Mais dans std::vector::insert
il y a 3 choses supplémentaires effectuées et cela a un impact sur les performances . 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)
}
La réponse courte est que std::insert_iterator
vous permet d'insérer n'importe où dans le conteneur:
//insert at index 2
auto it = std::inserter(v, v.begin() + 2);
*it = 4;
Pour ce faire, std :: vector doit déplacer les éléments existants après l'index 2 d'une place dans l'exemple ci-dessus. Ceci est l'opération O(n)
sauf si vous insérez à la fin car il n'y a rien d'autre à faire descendre. Mais il doit encore effectuer des vérifications pertinentes qui entraînent une pénalité de O(1)
perf. Pour les listes chaînées, vous pouvez insérer n'importe où dans O(1)
time pour éviter toute pénalité. Le back_inserter
insère toujours à la fin, donc pas de pénalité non plus.