vector<int> v;
v.Push_back(1);
v.Push_back(v[0]);
Si le deuxième Push_back provoque une réallocation, la référence au premier entier du vecteur ne sera plus valide. Ce n'est donc pas sûr?
vector<int> v;
v.Push_back(1);
v.reserve(v.size() + 1);
v.Push_back(v[0]);
Cela le rend sûr?
Il semble que http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-closed.html#526 a résolu ce problème (ou quelque chose de très similaire) en tant que défaut potentiel dans la norme:
1) Les paramètres pris par référence const peuvent être modifiés lors de l'exécution de la fonction
Exemples:
Étant donné std :: vector v:
v.insert (v.begin (), v [2]);
v [2] peut être modifié en déplaçant des éléments du vecteur
La résolution proposée était que ce n'était pas un défaut:
vector :: insert (iter, value) est requis pour fonctionner car la norme ne donne pas la permission de ne pas fonctionner.
Oui, c'est sûr, et les implémentations de bibliothèque standard sautent à travers des cercles pour le faire.
Je crois que les implémenteurs remontent cette exigence au 23.2/11 d'une manière ou d'une autre, mais je ne peux pas comprendre comment, et je ne peux pas non plus trouver quelque chose de plus concret. Le mieux que je puisse trouver est cet article:
http://www.drdobbs.com/cpp/copying-container-elements-from-the-c-li/240155771
L'inspection des implémentations de libc ++ et libstdc ++ montre qu'elles sont également sûres.
La norme garantit même la sécurité de votre premier exemple. Citant C++ 11
[sequence.reqmts]
3 Dans les tableaux 100 et 101 ...
X
désigne une classe de conteneur de séquence,a
désigne une valeur deX
contenant des éléments de typeT
, .. .t
désigne une lvalue ou une const rvalue deX::value_type
16 Tableau 101 ...
Expression
a.Push_back(t)
Type de retourvoid
Sémantique opérationnelle Ajoute une copie det.
Nécessite:T
doit êtreCopyInsertable
dansX
. Conteneurbasic_string
,deque
,list
,vector
Ainsi, même si ce n'est pas exactement trivial, l'implémentation doit garantir qu'elle n'invalidera pas la référence lors de la Push_back
.
Il n'est pas évident que le premier exemple soit sûr, car l'implémentation la plus simple de Push_back
serait de réallouer d'abord le vecteur, si nécessaire, puis de copier la référence.
Mais au moins, il semble être sûr avec Visual Studio 2010. Son implémentation de Push_back
fait un traitement spécial du cas lorsque vous repoussez un élément dans le vecteur. Le code est structuré comme suit:
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
...
}
}
Ce n'est pas une garantie de la norme, mais comme un autre point de données, v.Push_back(v[0])
est sûr pour libc ++ de LLVM .
libc ++ std::vector::Push_back
appelle __Push_back_slow_path
lorsqu'il a besoin de réallouer la mémoire:
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.
}
La première version n'est certainement PAS sûre:
Les opérations sur les itérateurs obtenues en appelant un conteneur de bibliothèque standard ou une fonction membre de chaîne peuvent accéder au conteneur sous-jacent, mais ne doivent pas le modifier. [Remarque: en particulier, les opérations de conteneur qui invalident les itérateurs entrent en conflit avec les opérations sur les itérateurs associés à ce conteneur. - note de fin]
de la section 17.6.5.9
Notez que c'est la section sur les races de données, à laquelle les gens pensent normalement en conjonction avec le filetage ... mais la définition réelle implique des relations "avant" et je ne vois aucune relation d'ordre entre les multiples effets secondaires de Push_back
en jeu ici, à savoir que l'invalidation de référence ne semble pas être définie comme ordonnée par rapport à la construction par copie du nouvel élément de queue.
C'est complètement sûr.
Dans votre deuxième exemple, vous avez
v.reserve(v.size() + 1);
ce qui n'est pas nécessaire car si le vecteur sort de sa taille, cela impliquera le reserve
.
Le vecteur est responsable de ce genre de choses, pas vous.