6
votes

std :: vecteur :: efface (la position d'itérateur) n'invoque pas nécessairement le destructeur de l'élément correspondant

en supposant que j'ai un std :: vecteur v de 5 éléments,

v.térase (v.begin () + 2) Retirez le 3ème élément.

stl Vecteur déplacera le 4ème et le 5ème élément, puis destruct le 5ème élément.

IE ELLE EXASING i dans un Vecteur ne garantit pas que ith destructor est appelé. Pour std :: Liste , ce n'est pas le cas. Effacer avec l'élément invoque ith le destructeur de l'élément.

Qu'est-ce que stl dit à propos de ce comportement?

Ceci est le code pris de mon System's stl_vector.h: xxx


10 commentaires

À moins que vous ne posiez à propos des années 80, cela n'est pas appelé la STL mais la bibliothèque standard C ++ ...


Ce serait sûrement plutôt Daft. Je ne l'ai pas testé, mais je suis sûr que ce que vous décrivez n'est pas le cas. Vous ne pouvez pas détruire un élément différent de celui que vous effacer . Et si nous avons nom de fichier dans notre vecteur et le nom de fichier ~ ~ () {retirez (nom.c_str ()); } ?


Euh, il semblerait que je vois un scénario similaire à celui de l'OP décrit. Je mettais une classe avec un int, stocker l'index actuel dans l'INT dans un vecteur, puis supprimez l'élément avec la valeur 2 et le destructeur est effectivement appelé l'élément de 9 en informatique.


@MATSPETERSSON DROITE, après avoir réaffecté la question, il est logique de faire un sens: la copie serait utilisée pour déplacer les éléments. Op, excuses de mal interpréter la question. Mais un échantillon de code compilable aurait aidé.


J'ai vu ce que j'ai décrit à partir d'une application d'exemple, j'ai alors confirmé ce que j'ai décrit à partir du code source de mise en œuvre effaçable (). Mais je suis un peu confus parce que je pense que c'est un comportement "dangereux" ...


Tant que les éléments stockés dans le vecteur ont correctement implémenté des constructeurs de copies, je suppose qu'il n'y a pas de danger dans ce comportement. Oui, le dernier élément destructeur de l'élément est appelé, mais seulement après que cet élément a été copié à une position avant-avant-dernière position.


@ Alex1985: Eh bien, si nous "utilisons" le destructeur de faire quelque chose de significatif, il est important que l'élément du vecteur est réellement détruit, car le contenu n'est probablement pas le même pour tous les éléments ...


@Matspetersson qui serait vrai si le type n'a pas de copie et de déplacement sensibles. En fin de compte, vous supprimez une copie de l'élément effacé.


Comment cela aide-t-il? Voir mon exemple ci-dessous, où l'élément avec la valeur 4 est détruit. Si nous "utilisons" cette valeur à l'intérieur du destructeur (pour les statistiques, la suppression d'un fichier, ou autre chose), il fera la mauvaise chose. Je comprends que "c'est comme ça que ça marche", et je ne discute pas que cela devrait être changé, c'est quelque chose que je suis surpris de savoir (montrant clairement que je n'ai jamais vraiment pris soin de quel ordre / lequel mes objets dans un vecteur se détruit).


Voir ma réponse pour pourquoi il n'y a pas de danger. L'Opérateur d'affectation doit gérer le «Nettoyage» actuel (ou tout ce qui dépend de l'état de l'objet actuel) correctement avant de remplacer les éléments avec les valeurs de l'objet attribué.


6 Réponses :


2
votes

Contrairement à STD :: Liste , std :: Vecteur contient ses éléments contiguës. Ainsi, lorsqu'un élément est effacé du milieu du conteneur, il serait plus logique de copier attribuer tous les éléments à décaler. Dans ce scénario, le destructeur du dernier élément décalé serait appelé. Cela évite une réaffectation de l'ensemble des données du vecteur.


0 commentaires

0
votes

Voici un petit programme qui montre le problème, et oui, si vous comptez sur le destructeur étant appelé pour cet objet très, vous devez faire autre chose que ce que ce code fait: xxx

La sortie est la suivante: xxx

une variante pour résoudre ce problème serait de stocker un pointeur (intelligent) et de copier manuellement le pointeur, puis Supprimer ça.


2 commentaires

L'opération d'affectation devrait s'occuper de toute action critique qui dépend de la valeur actuelle de x (ou tous les membres de l'objet) avant de l'écraser avec hache (ou données de la autre objet). Je dirais que toute opération d'affectation qui ignore cela est imparfaite. Vous pouvez utiliser une sorte de nettoyage () appelé 1) dans l'opération d'affectation avant de réaffecter d'autres valeurs et 2) dans le destructeur.


Où la solution la plus charmante serait Raii, je suppose.



4
votes

La norme C ++ 11 23.3.6.5/4 dit (l'accent est mis à moi):

Complexité: Le destructeur de T s'appelle le nombre de fois égal au nombre d'éléments effacés , mais l'opérateur d'affectation de déplacement de T s'appelle le nombre de fois égal au nombre d'éléments. dans le vecteur après les éléments effacés.

avait la mise en œuvre appelée le destructeur sur le 3ème élément, il ne serait pas conforme.

En effet, supposons que le destructeur soit appelé le 3ème élément. Puisque qu'un seul élément est effacé, le desctructeur ne peut plus être appelé.

Après l'appel destructeurs, la 3ème position contient la mémoire brute (pas un objet entièrement construcd t ). Par conséquent, la mise en œuvre doit appeler le constructeur de déplacement pour passer de la 4ème position au 3ème.

Il ne peut pas détruire le 4ème élément (car il ne peut plus appeler le destructeur), puis passer du 5ème au 4ème élément, il doit appeler l'opérateur d'attribution de déplacement.

À ce stade, la mise en œuvre doit toujours diminuer la taille par 1 et détruire le 5ème élément mais, comme nous l'avons vu, aucun autre appel du destrucor n'est autorisé. (Notez également que l'opérateur d'affectation de mouvement ne serait pas appelé deux fois comme requis par la norme.) QED.


0 commentaires

3
votes

La norme dit que ceci est attendu, la spécification pour vecteur :: Effacer (Const_iterator) (dans le tableau des exigences du conteneur de séquence) indique que les exigences de cette fonction sont les suivantes:

pour vecteur et deque , t doit être mobleasseignable . .

La raison de la nécessité d'exiger MOVEASSIVELABLE est que chacun des éléments suivants sera (déplacement) assigné sur l'élément devant eux et le dernier élément détruit.

En théorie, il aurait été possible que l'URS original de l'ait fait différemment et d'avoir détruit l'élément effacé comme prévu, mais il y a de bonnes raisons qui n'ont pas été choisis. Si vous seul détruisez l'élément effacé que vous laissez un "trou" dans le vecteur, qui n'est pas une option (le vecteur devrait se rappeler où les trous étaient et si un utilisateur indique v [ 5] Le vecteur devrait se rappeler qu'il y a un trou là-bas et retourner V [6] à la place.) Il est donc nécessaire de "mélanger" les éléments ultérieurs pour remplir le trou. Cela aurait pu être fait en détruisant le nième élément en place (c'est-à-dire V [n]. ~ Valeur_type () ), puis à l'aide de placement neuf pour créer un nouvel objet à cette emplacement (c.-à-d. :: nouveau ((((((((vide *) & v [n]) valeur_type (std :: déplacer (v [n + 1])) ) puis faites de même pour chaque élément suivant, jusqu'à ce que Vous arrivez à la fin, cependant, cela entraînerait une performance bien pire dans de nombreux cas. Si les éléments existants ont alloué la mémoire, par exemple Les conteneurs eux-mêmes sont-ils eux-mêmes, puis l'affectant peut leur permettre de réutiliser cette mémoire, mais de les détruire, puis de la construction de nouveaux éléments nécessiterait une mémoire de réaffectation et de réaffectation, ce qui pourrait être beaucoup plus lent et pourrait fragmenter le tas. Il y a donc une très bonne raison pour nous assurer la modification des valeurs des éléments, sans modifier nécessairement leur identité.

Ce n'est pas le cas de std :: list et d'autres conteneurs car ils ne stockent pas des éléments dans un bloc contigu comme vecteur et deque , de sorte que l'élimination d'un seul élément implique simplement de régler les liens entre les éléments voisins et il n'est pas nécessaire de "mélanger" d'autres éléments dans le bloc pour prendre la position vide.


2 commentaires

Bonjour Jonathan: Je ne pense pas que nous puissions appeler le destructeur sur les éléments suivants car (voir ma réponse) La mise en œuvre est autorisée à appeler le destructeur une seule fois. Corrigez-moi si j'ai tort, s'il-vous plait.


@Cassio, vous avez absolument raison - j'aurais dû être clair que ma pensée "en théorie" n'est pas conforme, je m'explique simplement de ce qui devrait être fait pour détruire l'élément effacé (comme attendu) sans laisser "Trou" dans la séquence et pourquoi la STL n'a pas choisi cette option.



3
votes

Ceci est un comportement parfaitement valide. @Cassio Neri a souligné pourquoi il est requis par la norme.

Short: P>

" std :: vecteur :: efface (position d'itérateur) n'appelle pas nécessairement le destructeur de l'élément correspondant em> "[op; Titre] mais un destructeur est invoqué, manipulant les données des éléments correspondants qui ont été transférés sur un autre objet (via le constructeur de déplacement vers le déplacé-de em> ou via Raii à l'instance temporaire) . strong> h3>

long: p>

Pourquoi vous n'avez pas à s'appuyer sur le destructeur em> à appeler. h2>

Je vais donner quelques astuces pourquoi vous ne devriez pas vous inquiéter du tout, quel destructeur est appelé dans ce cas. P>

Considérez la petite classe suivante p> xxx pré>

Si vous gérez correctement votre objet d'objet, vous n'avez pas besoin de vous inquiéter du fait que le destructeur est appelé correctement. P> xxx pré>

Votre opérateur d'affectation de mouvement doit transférer l'état de L'objet "écrasé" dans l'objet "déplacé de" ( rhs code> ici) afin que ce soit destructeurs prendra une action appropriée (s'il y a quelque chose que le destructeur doit prendre en charge). Peut-être devriez-vous utiliser quelque chose comme une fonction de membre "Swap" pour effectuer le transfert pour vous. P>

Si votre objet est non mobile, vous devez gérer le "nettoyage" (ou quelle que soit l'action qui s'appuie sur l'état actuel de l'objet) de l'objet effacé dans l'opération d'attribution de copie avant de copier les nouvelles données dans l'objet. p> xxx pré>

ici, nous utilisons Raii et encore le Swap code> (qui peut toujours être une fonction de membre aussi; mais le test n'a qu'un seul pointeur ...). Le destructeur de TMP code> fera des choses confortables. P>

Faites un petit test: H2>
Construct
Memory 012C9F18 claimed.
Memory 012CA0F0 claimed.
Memory 012CA2B0 claimed. // 2nd element
Memory 012CA2F0 claimed.
Memory 012CA110 claimed.
Erase
Move assignment from 012CA2F0
Move assignment from 012CA110
Memory 012CA2B0 will be deleted. // destruction of the data of 2nd element
Kick-off
Memory 012C9F18 will be deleted.
Memory 012CA0F0 will be deleted.
Memory 012CA2F0 will be deleted.
Memory 012CA110 will be deleted.


0 commentaires

2
votes

En référence à l'exemple de Mats Peterson, peut-être que cet exemple montrera plus clairement que les détruire 2 se produisent vraiment, nous n'avons tout simplement pas de destructeur disponible pour le type intégré où nous pouvons facilement ajouter l'énoncé d'impression:

Destroy Integer=0
Destroy Integer=0
Destroy Integer=1
Destroy Integer=0
Destroy Integer=1
Destroy Integer=2
Destroy Integer=0
Destroy Integer=1
Destroy Integer=2
Destroy Integer=3
Destroy Integer=0
Destroy Integer=1
Destroy Integer=2
Destroy Integer=3
Destroy Integer=4
Erasing ...
Destroy Integer=2
copy x=3
Destroy Integer=2
Destroy Integer=3
Destroy Integer=3
copy x=4
Destroy Integer=3
Destroy Integer=4
Destroy Integer=4


0 commentaires