IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

SL & STL C++ Discussion :

fonctionnement de std::vector


Sujet :

SL & STL C++

  1. #1
    Inactif  


    Homme Profil pro
    Inscrit en
    Novembre 2008
    Messages
    5 288
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Secteur : Santé

    Informations forums :
    Inscription : Novembre 2008
    Messages : 5 288
    Par défaut fonctionnement de std::vector
    Bonjour à tous

    J'ai un problème pour comprendre et résoudre une erreur d'exécution lors de l'ajout d'un élément dans un vecteur.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class A { /*complexe, plein de sous-classe... */ };
    std::vector<A> v;
    A a;
    v.push_back(a);
    v.back().method();
    Ce code ne produit pas d'erreur, j'ai la séquence d'appel constructeur/destructeur attendue : default construteur A (variable a) -> copy constructeur A (dans le push_back) -> destructeur A (variable a) -> appel méthode, mon vecteur contient bien 1 élément et la méthode est exécutée.

    Par contre, avec ce code :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    class A { /*complexe, plein de sous-classe... */ };
    std::vector<A> v;
    v.resize(v.size()+1);
    v.back().method();
    J'ai une erreur. Mon objet est détruit directement après le resize et mon vecteur est vide. J'ai la séquence suivante : default constructeur (dans le resize) -> default destructeur (dans le resize) -> appel méthode = erreur (normal puisque mon objet est détruit).

    1. Pourquoi ce comportement différent ?
    2. Comment trouver l'erreur ?
    A priori, mon constructeur par défaut est correct puisqu'il ne produit pas d'erreur dans le premier code...

    Merci pour vos conseils

    PS: en complément, pas d'exception produite.
    système Ubuntu 64b, gcc 4.3

  2. #2
    Membre averti
    Inscrit en
    Février 2008
    Messages
    20
    Détails du profil
    Informations forums :
    Inscription : Février 2008
    Messages : 20
    Par défaut
    Pourquoi tu utilises resize ? Juste pour ajouter un élément ?

    Dans ce cas je ferais plutôt :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    std::vector<A> v;
    v.push_back(A);
    v.back().method();

  3. #3
    Membre éprouvé
    Avatar de _skip
    Homme Profil pro
    Développeur d'applications
    Inscrit en
    Novembre 2005
    Messages
    2 898
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : Suisse

    Informations professionnelles :
    Activité : Développeur d'applications
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Novembre 2005
    Messages : 2 898
    Par défaut
    T'es sur que c'est pas ta classe qui est bizarre, ici :

    Citation Envoyé par gbdivers Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class A { /*complexe, plein de sous-classe... */ };
    std::vector<A> v;
    A a;
    v.push_back(a);
    v.back().method();
    constructeur/destructeur attendue : default construteur A (variable a) -> copy constructeur A (dans le push_back) -> destructeur A (variable a) -> appel méthode, mon vecteur contient bien 1 élément et la méthode est exécutée.
    Je vois pas pourquoi le destructeur de a est appelé puisque ce dernier est encore à portée.

    Si tu essaies avec un objet simple comme une std::pair<int, int>, ça donne quoi?

    Resize est censé faire autant de push_back() qu'il faut avec par défaut des A() pour atteindre la taille donnée, je vois pas pourquoi ça ne fonctionnerait pas.

  4. #4
    Membre Expert

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Par défaut
    Peut-être que resize utilise l'opérateur d'affectation et pas le constructeur par copie ? Tu as vérifié que tes objets définissent bien l'opérateur= ?

    Edit : Après vérification, l'implémentation de resize() dans la STL livré avec vs2005 ne fait pas d'appel à l'opérateur=.

  5. #5
    Membre éprouvé
    Avatar de _skip
    Homme Profil pro
    Développeur d'applications
    Inscrit en
    Novembre 2005
    Messages
    2 898
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : Suisse

    Informations professionnelles :
    Activité : Développeur d'applications
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Novembre 2005
    Messages : 2 898
    Par défaut
    Si je me suis pas gourré, dans mingw, lui même basé sur gcc, resize provoque un appel à insert :

    On constate cette ligne :
    value_type __x_copy = __x;

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    void
        vector<_Tp,_Alloc>::
        _M_fill_insert(iterator __position, size_type __n, const value_type& __x)
        {
          if (__n != 0)
          {
            if (size_type(this->_M_impl._M_end_of_storage - this->_M_impl._M_finish) >= __n)
    	  {
               value_type __x_copy = __x;
    	   const size_type __elems_after = end() - __position;
    	   iterator __old_finish(this->_M_impl._M_finish);
    	   if (__elems_after > __n)
    	     {
    	       std::uninitialized_copy(this->_M_impl._M_finish - __n,
    				       this->_M_impl._M_finish,
    				       this->_M_impl._M_finish);
    	       this->_M_impl._M_finish += __n;
    	       std::copy_backward(__position, __old_finish - __n, __old_finish);
    	       std::fill(__position, __position + __n, __x_copy);
    	     }
    	   else
    	     {
    	       std::uninitialized_fill_n(this->_M_impl._M_finish,
    					 __n - __elems_after,
    					 __x_copy);
    	       this->_M_impl._M_finish += __n - __elems_after;
    	       std::uninitialized_copy(__position, __old_finish, this->_M_impl._M_finish);
    	       this->_M_impl._M_finish += __elems_after;
    	       std::fill(__position, __old_finish, __x_copy);
    	     }
    	  }
            else
    	  {
    	    const size_type __old_size = size();
    	    const size_type __len = __old_size + std::max(__old_size, __n);
    	    iterator __new_start(this->_M_allocate(__len));
    	    iterator __new_finish(__new_start);
    	    try
    	      {
    		__new_finish = std::uninitialized_copy(begin(), __position,
    						       __new_start);
    		__new_finish = std::uninitialized_fill_n(__new_finish, __n, __x);
    		__new_finish = std::uninitialized_copy(__position, end(),
    						       __new_finish);
    	      }
    	    catch(...)
    	      {
    		std::_Destroy(__new_start,__new_finish);
    		_M_deallocate(__new_start.base(),__len);
    		__throw_exception_again;
    	      }
    	    std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish);
    	    _M_deallocate(this->_M_impl._M_start,
    			  this->_M_impl._M_end_of_storage - this->_M_impl._M_start);
    	    this->_M_impl._M_start = __new_start.base();
    	    this->_M_impl._M_finish = __new_finish.base();
    	    this->_M_impl._M_end_of_storage = __new_start.base() + __len;
    	  }
          }
        }

  6. #6
    Membre Expert

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Par défaut
    Citation Envoyé par _skip Voir le message
    Si je me suis pas gourré, dans mingw, lui même basé sur gcc, resize provoque un appel à insert :

    On constate cette ligne :
    value_type __x_copy = __x;
    ça reste du constructeur par copie. ça aurait été une affectation si le code se présentait comme ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    value_type __x_copy;
    __x_copy = x ; // op =
    Pour l'erreur original, j'avoue que je ne vois pas du tout. Il faudrait débugger en pas à pas pour voir ce que fait réellement resize().

  7. #7
    Inactif  


    Homme Profil pro
    Inscrit en
    Novembre 2008
    Messages
    5 288
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Secteur : Santé

    Informations forums :
    Inscription : Novembre 2008
    Messages : 5 288
    Par défaut
    Merci pour vos pistes.
    J'ai ajouté l'opérateur d'affectation (je ne savais pas qu'il y en avait un par défaut, je m'attendais à une erreur).

    Pour compléter :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
     
    static int s_id = 0;
    class A 
    { 
     int id;
     
     A()
     {
      id = ++s_id;
      cout << "constructor : id " << id << endl;
      ...
     }
     
     A(const A& a)
     {
      id = ++s_id;
      cout << "copy constructor : id " << id << endl;
      ...
     }
     
     ~A()
     {
      cout << "destructor : id " << id << endl;
      ...
     }
     
     A& A::operator= (const A& a)
     {
      cout << "operator= : id " << id << endl;
      ...
     }
     
    /*complexe, plein de sous-classe... */ 
    };
     
    class B
    {
     std::vector<A> v;
     
     void method2()
     {
       cout << "1. vector size : " << v.size() << endl;
       {
         A a;
         v.push_back(a);
       }
     
       cout << endl << "2. vector size : " << v.size() << endl;
       {
         v.push_back(A());
       }
     
       cout << endl << "3. vector size : " << v.size() << endl;
       {
        v.resize(v.size()+5);
       }
     
       cout << endl << "4. vector size : " << v.size() << endl;
     }
    };
    Et les messages affichés (id est un numéro unique pour chaque objet créé) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    1. vector size : 0
    constructor : id 1
    copy constructor : id 2
    destructor : id 1
     
    2. vector size : 1
    constructor : id 3
    copy constructor : id 4
    destructor : id 3
     
    3. vector size : 2
    constructor : id 5
    copy constructor : id 6
    copy constructor : id 7
    copy constructor : id 8
    copy constructor : id 9
    copy constructor : id 10
    copy constructor : id 11
    delete : id 6
    delete : id 5
     
    Erreur de segmentation
    Donc seul le constructeur de copie est utilisé et pas l'opérateur d'affectation.
    Le programme plante dans le resize et n'arrive jamais à l'étape 4. Je ne sais pas ce qui génère l'erreur de segmentation.

    Pourquoi je n'utilise pas push_back ? en fait, c'est ce que je fais. Mais le programme d'origine que j'ai repris utilisait la méthode avec resize et je voulais comprendre le pourquoi du problème.

    Edit : je comprend pas très bien pourquoi il y a 6 appels au constructeur de copie dans l'étape 3 (et 2 appels au destructeur)...

    Edit2 : question subsidiaire : est-il possible de désactiver la création automatique de l'opérateur d'affectation par défaut ?

  8. #8
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Par défaut
    Citation Envoyé par gbdivers Voir le message
    Edit : je comprend pas très bien pourquoi il y a 6 appels au constructeur de copie dans l'étape 3 (et 2 appels au destructeur)...
    Je crois que c'est ça :
    3. vector size : 2
    constructor : id 5 -> Pour le second argument de resize(size_t, A=A()); (2)
    copy constructor : id 6 -> Pour le passage à insert (1)
    copy constructor : id 7 -> pour la construction du 1 élément
    copy constructor : id 8 -> pour la construction du 2 élément
    copy constructor : id 9 -> pour la construction du 3 élément
    copy constructor : id 10 -> pour la construction du 4 élément
    copy constructor : id 11 -> pour la construction du 5 élément
    delete : id 6 -> destruction de (1)
    delete : id 5 -> destruction de (2)

    Citation Envoyé par gbdivers Voir le message
    Edit2 : question subsidiaire : est-il possible de désactiver la création automatique de l'opérateur d'affectation par défaut ?
    Oui tu le déclares privé dans A mais ne le définit pas. :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    class A
    {
    private:
       A&operator=(A const &); // surtout ne pas lui donner d'implémentation
    };
    Ton problème est en revanche très étrange. Ta classe A n'aurait-elle pas un pointeur nu comme membre détruit dans le destructeur ?

  9. #9
    Inactif  


    Homme Profil pro
    Inscrit en
    Novembre 2008
    Messages
    5 288
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Secteur : Santé

    Informations forums :
    Inscription : Novembre 2008
    Messages : 5 288
    Par défaut
    Non Monsieur. Personne n'est nu ici. Nous sommes entre classes civilisées !

    Bon, plus sérieusement, c'est quoi un "pointeur nu" ?

    Edit : Ok, j'ai compris : un "vecteur nu" = "vecteur null" !
    En effet, c'était bien ça. J'avais un pointeur sur un thread qui trainait quelque part et qui était copié bêtement dans un constructeur de recopie. Lors du double appel au destructeur, le premier détruisait le thread et le second plantais sur un delete. Comme je bloquais sur cette erreur, je ne suis pas allé voir si mon thread tournait encore...

    En fait, la méthode avec push_back ne produisait pas d'erreur simplement parce que je n'utilisait mon thread derrière. Mais il était bien détruit quand même.

    Merci à vous

  10. #10
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Par défaut
    Citation Envoyé par gbdivers Voir le message
    Bon, plus sérieusement, c'est quoi un "pointeur nu" ?
    C'est un pointeur bête

    Un pointeur nu est un pointeur tel quel :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    type *pointeur_nu(new type);
    par opposé à un pointeur intelligent qui est encapsulé dans une classe qui lui donne de l'intelligence (RAII) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    pointeur_intelligent<type*> pointeur(new type);
    Lectures conseillées :
    F.A.Q. : Qu'est-ce qu'un pointeur intelligent ?
    F.A.Q. : R.A.I.I.
    Tutoriels Pointeurs intelligents de Loïc Joly

  11. #11
    Inactif  


    Homme Profil pro
    Inscrit en
    Novembre 2008
    Messages
    5 288
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Secteur : Santé

    Informations forums :
    Inscription : Novembre 2008
    Messages : 5 288
    Par défaut
    Bon, j'avais compris de travers ("pointeur nu" != "pointeur null") mais c'est bien un problème d'allocation... Ca m'apprendra à utiliser encore des pointeurs bêtes (ou plus précisément de travailler sur un programme qui les utilise).

    Par contre, c'est étonnant que cela de génère pas d'exception.

    Merci encore

  12. #12
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Par défaut
    Citation Envoyé par gbdivers Voir le message
    Edit : Ok, j'ai compris : un "vecteur nu" = "vecteur null" !
    Non, cf ci-dessus.
    Citation Envoyé par gbdivers Voir le message
    En effet, c'était bien ça. J'avais un pointeur sur un thread qui trainait quelque part et qui était copié bêtement dans un constructeur de recopie. Lors du double appel au destructeur, le premier détruisait le thread et le second plantais sur un delete. Comme je bloquais sur cette erreur, je ne suis pas allé voir si mon thread tournait encore...
    D'où les pointeurs intelligents

  13. #13
    Expert confirmé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Décembre 2003
    Messages
    3 549
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Décembre 2003
    Messages : 3 549
    Par défaut
    Ce code est parfaitement correct et ne produit pas d'erreur de segmentation.
    Quel est le problème ?

  14. #14
    Inactif  


    Homme Profil pro
    Inscrit en
    Novembre 2008
    Messages
    5 288
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Secteur : Santé

    Informations forums :
    Inscription : Novembre 2008
    Messages : 5 288
    Par défaut
    Évidement ce code est correcte et c'est bien là le problème.
    Quand il y a une erreur (de conception dans ce cas) dans le "/*complexe, plein de sous-classe... */" qui déclenche une erreur "à distance", il est très difficile de la trouver.

    L'interrogation ici porte sur la différence de comportement entre push_back et resize. On pourrais penser, a priori, que les 2 méthodes sont équivalentes. En fait, ce n'est pas le cas : resize crée 2 objets temporaires (ce qui, en général, ne sera pas pénalisant en terme de rapidité).

  15. #15
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Par défaut
    Citation Envoyé par gbdivers Voir le message
    En fait, ce n'est pas le cas : resize crée 2 objets temporaires (ce qui, en général, ne sera pas pénalisant en terme de rapidité).
    resize ne créé pas 2 objets temporaires. Il créé 2 objets dans le vecteur. vect.size /*après */ = vect.size() /* avant */ + 2. Le constructeur sur le type d'élément est appelé 2 fois (en général, le constructeur par copie)
    Si tu veux juste allouer la mémoire mais sans créer d'objets, tu as reserve. reserve alloue de la mémoire mais le vecteur possède toujours le même nombre d'éléments (vect.size() /* avant */ == vect.size() /* après */. Aucun constructeur sur le type d'éléments n'est appelé.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. std::vector : dynamique ou statique, pile et tas
    Par salseropom dans le forum SL & STL
    Réponses: 7
    Dernier message: 24/01/2005, 13h22
  2. std::sort() sur std::vector()
    Par tut dans le forum SL & STL
    Réponses: 20
    Dernier message: 05/01/2005, 19h15
  3. char[50] et std::vector<>
    Par tut dans le forum SL & STL
    Réponses: 9
    Dernier message: 12/10/2004, 13h26
  4. Réponses: 8
    Dernier message: 26/08/2004, 18h59
  5. Sauvegarde std::vector dans un .ini
    Par mick74 dans le forum MFC
    Réponses: 2
    Dernier message: 12/05/2004, 13h30

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo