web-dev-qa-db-fra.com

Vérifier si un itérateur est valide

Existe-t-il un moyen de vérifier si un itérateur (qu'il provienne d'un vecteur, d'une liste, d'un deque ...) est (toujours) déférencable, c'est-à-dire qu'il n'a pas été invalidé?

J'utilise try-catch, mais existe-t-il un moyen plus direct de le faire?

Exemple: (qui ne fonctionne pas)

list<int> l;
for (i = 1; i<10; i++) {
    l.Push_back(i * 10);
}

itd = l.begin();
itd++;
if (something) {
    l.erase(itd);
}

/* now, in other place.. check if itd points to somewhere meaningful */
if (itd != l.end())
{
    //  blablabla
}
62
huff

Je suppose que vous voulez dire "est un itérateur valide", qu'il n'a pas été invalidé en raison de modifications apportées au conteneur (par exemple, l'insertion/l'effacement vers/depuis un vecteur). Dans ce cas, non, vous ne pouvez pas déterminer si un itérateur est (en toute sécurité) déréférençable.

59
Jason Govig

Comme l'a dit jdehaan, si l'itérateur n'a pas été invalidé et pointe vers un conteneur, vous pouvez le vérifier en le comparant à container.end().

Notez cependant que si l'itérateur est singulier - parce qu'il n'a pas été initialisé ou qu'il est devenu invalide après une opération de mutation sur le conteneur (les itérateurs du vecteur sont invalidés lorsque vous augmentez la capacité du vecteur, par exemple ) - la seule opération que vous êtes autorisé à effectuer sur celle-ci est l'affectation. En d'autres termes, vous ne pouvez pas vérifier si un itérateur est singulier ou non.

std::vector<int>::iterator iter = vec.begin();
vec.resize(vec.capacity() + 1);
// iter is now singular, you may only perform assignment on it,
// there is no way in general to determine whether it is singular or not
25
avakar

Réponse non portable: Oui - dans Visual Studio

Les itérateurs STL de Visual Studio ont un mode de "débogage" qui fait exactement cela. Vous ne voudriez pas activer cela dans les constructions de vaisseaux (il y a des frais généraux) mais utile dans les constructions vérifiées.

Lisez à ce sujet sur VC10 ici (ce système peut et en fait change chaque version, alors trouvez les documents spécifiques à votre version).

Edit Aussi, je devrais ajouter: les itérateurs de débogage dans Visual Studio sont conçus pour exploser immédiatement lorsque vous les utilisez (au lieu d'un comportement non défini); ne pas autoriser la "requête" de leur état.

10
Terry Mahaffey

Habituellement, vous le testez en vérifiant s'il est différent de la fin (), comme

if (it != container.end())
{
   // then dereference
}

De plus, l'utilisation de la gestion des exceptions pour remplacer la logique est mauvaise en termes de conception et de performances. Votre question est très bonne et mérite définitivement un remplacement dans votre code. La gestion des exceptions comme le nom l'indique ne doit être utilisée que pour de rares problèmes inattendus.

9
jdehaan

Existe-t-il un moyen de vérifier si un itérateur (qu'il provienne d'un vecteur, d'une liste, d'un deque ...) est (toujours) déréférençable, c'est-à-dire qu'il n'a pas été invalidé?

Non, il n'y en a pas. Au lieu de cela, vous devez contrôler l'accès au conteneur pendant que votre itérateur existe, par exemple:

  • Votre thread ne doit pas modifier le conteneur (invalider l'itérateur) pendant qu'il utilise toujours un itérateur instancié pour ce conteneur

  • S'il y a un risque que d'autres threads puissent modifier le conteneur pendant que votre thread est en cours d'itération, alors pour rendre ce scénario sûr pour les threads, votre thread doit acquérir une sorte de verrouillage sur le conteneur (afin qu'il empêche d'autres threads de modifier le conteneur pendant il utilise un itérateur)

Les solutions de contournement comme intercepter une exception ne fonctionneront pas.

Ceci est une instance spécifique du problème plus général, "puis-je tester/détecter si un pointeur est valide?", La réponse est généralement "non, vous ne pouvez pas le tester: à la place, vous devez gérer toutes les allocations de mémoire et suppressions afin de savoir si un pointeur donné est toujours valide ".

7
ChrisW

Essayer et attraper n'est pas sûr, vous ne le lancerez pas ou du moins rarement si votre itérateur est "hors limites".

ce que dit alemjerus, un itérateur peut toujours être déréférencé. Peu importe la laideur qui se cache en dessous. Il est tout à fait possible d'itérer dans d'autres zones de la mémoire et d'écrire dans d'autres zones susceptibles de conserver d'autres objets. J'ai regardé le code, en regardant les variables changer sans raison particulière. C'est un bug vraiment difficile à détecter.

Il est également judicieux de se rappeler que l'insertion et la suppression d'éléments peuvent potentiellement invalider toutes les références, pointeurs et itérateurs.

Mon meilleur conseil serait de garder vos itérateurs sous contrôle, et de toujours garder un itérateur "fin" à portée de main pour pouvoir tester si vous êtes en "fin de ligne" pour ainsi dire.

3
daramarak

Existe-t-il un moyen de vérifier si un itérateur est déréférençable

Oui, avec gcc débogage de conteneurs disponible sous la forme GNU extensions. Pour std::list vous pouvez utiliser __gnu_debug::list au lieu. Le code suivant sera abandonné dès que l'on tentera d'utiliser un itérateur non valide. Comme les conteneurs de débogage imposent une surcharge supplémentaire, ils ne sont destinés qu'au débogage.

#include <debug/list>

int main() {
  __gnu_debug::list<int> l;
  for (int i = 1; i < 10; i++) {
    l.Push_back(i * 10);
  }

  auto itd = l.begin();
  itd++;
  l.erase(itd);

  /* now, in other place.. check if itd points to somewhere meaningful */
  if (itd != l.end()) {
    //  blablabla
  }
}

$ ./a.out 
/usr/include/c++/7/debug/safe_iterator.h:552:
Error: attempt to compare a singular iterator to a past-the-end iterator.

Objects involved in the operation:
    iterator "lhs" @ 0x0x7ffda4c57fc0 {
      type = __gnu_debug::_Safe_iterator<std::_List_iterator<int>, std::__debug::list<int, std::allocator<int> > > (mutable iterator);
      state = singular;
      references sequence with type 'std::__debug::list<int, std::allocator<int> >' @ 0x0x7ffda4c57ff0
    }
    iterator "rhs" @ 0x0x7ffda4c580c0 {
      type = __gnu_debug::_Safe_iterator<std::_List_iterator<int>, std::__debug::list<int, std::allocator<int> > > (mutable iterator);
      state = past-the-end;
      references sequence with type 'std::__debug::list<int, std::allocator<int> >' @ 0x0x7ffda4c57ff0
    }
Aborted (core dumped)
1
ks1322

Dans certains des conteneurs STL, l'itérateur actuel devient invalide lorsque vous effacez la valeur actuelle de l'itérateur. Cela se produit car l'opération d'effacement modifie la structure de la mémoire interne du conteneur et l'opérateur d'incrémentation sur l'itérateur existant pointe vers des emplacements non définis.

Lorsque vous effectuez les opérations suivantes, l'itérateur est incémenté avant d'être transmis à la fonction d'effacement.

if (something) l.erase(itd++);

1
lava

Le type des paramètres de la fonction d'effacement de n'importe quel conteneur std (comme vous l'avez indiqué dans votre question, c'est-à-dire s'il s'agit d'un vecteur, d'une liste, d'un deque ...) est toujours itérateur de ce conteneur niquement.

Cette fonction utilise le premier itérateur donné pour exclure du conteneur l'élément vers lequel cet itérateur pointe et même ceux qui suivent. Certains conteneurs effacent un seul élément pour un itérateur, et certains autres conteneurs effacent tous les éléments suivis d'un itérateur (y compris l'élément pointé par cet itérateur) à la fin du conteneur. Si la fonction d'effacement reçoit deux itérateurs, alors les deux éléments, pointés par chaque itérateur, sont effacés du conteneur et tout le reste entre eux est également effacé du conteneur, mais le point est que chaque l'itérateur qui est passé à la fonction d'effacement de n'importe quel conteneur std devient invalide! Aussi:

Chaque itérateur qui pointait sur un élément qui a été effacé du conteneur devient invalide, mais il ne passe pas la fin du conteneur!

Cela signifie qu'un itérateur qui pointait sur un élément qui a été effacé du conteneur ne peut pas être comparé à container.end (). Cet itérateur n'est pas valide, il n'est donc pas déréférencable, c'est-à-dire que vous ne pouvez pas utiliser ni les opérateurs * ni ->, il n'est pas non plus incrémentable, c'est-à-dire que vous ne pouvez pas utiliser l'opérateur ++, et il n'est pas non plus décrémentable, c'est-à-dire que vous ne pouvez pas utiliser l'opérateur.

Ce n'est pas non plus comparable !!! I.E. vous ne pouvez même pas utiliser les opérateurs == ni! =

En fait, vous ne pouvez utiliser aucun opérateur déclaré et défini dans l'itérateur std. Vous ne pouvez rien faire avec cet itérateur, comme un pointeur nul.

Faire quelque chose avec un itérateur non valide arrête immédiatement le programme et provoque même le plantage du programme et une fenêtre de dialogue d'assertion apparaît. Il n'y a aucun moyen de continuer le programme, quelles que soient les options que vous choisissez, les boutons sur lesquels vous cliquez. Vous pouvez simplement terminer le programme et le processus en cliquant sur le bouton Abandonner.

Vous ne faites rien d'autre avec un itérateur non valide, à moins que vous ne puissiez le définir au début du conteneur ou simplement l'ignorer.

Mais avant de décider quoi faire avec un itérateur, vous devez d'abord savoir si cet itérateur est invalide ou non, si vous appelez la fonction d'effacement du conteneur que vous utilisez.

J'ai créé par moi-même une fonction qui vérifie, teste, sait et renvoie vrai si un itérateur donné est invalide ou non. Vous pouvez utiliser la fonction memcpy pour obtenir l'état de tout objet, élément, structure, classe et etc., et bien sûr, nous utilisons toujours la fonction memset au début pour effacer ou vider un nouveau tampon, une structure, une classe ou tout objet ou élément :

bool IsNull(list<int>::iterator& i) //In your example, you have used list<int>, but if your container is not list, then you have to change this parameter to the type of the container you are using, if it is either a vector or deque, and also the type of the element inside the container if necessary.
{
    byte buffer[sizeof(i)];
    memset(buffer, 0, sizeof(i));
    memcpy(buffer, &i, sizeof(i));
    return *buffer == 0; //I found that the size of any iterator is 12 bytes long. I also found that if the first byte of the iterator that I copy to the buffer is zero, then the iterator is invalid. Otherwise it is valid. I like to call invalid iterators also as "null iterators".
}

J'ai déjà testé cette fonction avant de la publier et j'ai constaté que cette fonction fonctionnait pour moi.

J'espère vraiment avoir pleinement répondu à votre question et vous a également beaucoup aidé!

0
user2133061