web-dev-qa-db-fra.com

Est-il sûr de Push_back un élément du même vecteur?

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?

125
Neil Kirk

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.

31
Nate Kohl

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.

21
Sebastian Redl

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 de X contenant des éléments de type T, .. . t désigne une lvalue ou une const rvalue de X::value_type

16 Tableau 101 ...

Expressiona.Push_back(t)Type de retourvoidSémantique opérationnelle Ajoute une copie de t. Nécessite: T doit être CopyInsertable dans X. 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.

13
Angew

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

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

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.

1
Ben Voigt

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.

0
Zaffy