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

Langage C++ Discussion :

[unique_ptr] Erreur en compilation.


Sujet :

Langage C++

  1. #21
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    393
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 393
    Points : 685
    Points
    685
    Par défaut
    Oui, c'est pareil
    (d'ailleurs, le move dans mon code ne sert a rien, puisque le unique_ptr est une rvalue)

    Citation Envoyé par Iradrille Voir le message
    Peux-tu expliquer ça ? En quoi un pointeur est synonyme d'allocation dynamique ?
    Et surtout, cela signifie qu'il n'a toujours pas compris ce qu’était un dangling pointeur (pointeur valide != pointeur différent de nullptr) et donc rien compris aux pointeurs intelligents.

    Mais bref, passons, ne le harcelons pas, il va croire a un complot contre lui...

  2. #22
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Citation Envoyé par mintho carmo Voir le message
    Et surtout, cela signifie qu'il n'a toujours pas compris ce qu’était un dangling pointeur
    C'est ce que j'essayais d'expliquer sans avoir le terme.

    Peut être qu'on exemple avec un pointeur et une allocation dynamique sera plus parlant.
    Pointeurs nus :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    int main() {
    	Entity *e = new EntityA;
    	{
    		CellMap cm; // fuite mémoire si ça throw : e ne sera pas libéré
     		cm.addEntity(e);
    		// Suis-je encore propriétaire de e ?
    		// L'objet pointé à-t-il été copié ? Dans ce cas, dois-je delete e ?
    	} 
    	// J'ai fini d'utiliser CellMap (qui utilise e);
    	// qu'est devenu e ? A-t-il été delete ?
    	// Dois-je le delete moi-même ?
     
    	return 0;
    }
    std::unique_ptr :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    int main() {
    	auto e = std::make_unique<EntityA>();
    	{
    		CellMap cm;
    		cm.addEntity(e);
    		// Je ne suis plus propriétaire de e, cm se démerde avec
    		// e n'est plus valide ici
    	}
    	// cm à été détruit, e aussi, à moins qu'il est été "donné" à un autre objet
    	// mais c'est pas mon problème, c'est celui du dev du framework
     
    	return 0;
    }

  3. #23
    Invité
    Invité(e)
    Par défaut
    Avec l'héritage, on est obligé de faire une allocation dynamique, étant donné que j'utilise du polymorphisme.

    Ce code ne compilera pas car la classe de base est abstraite :


    Celui là non plus sinon j'aurai eu un effet de slicing et adieu le polymorphisme :

    Code cpp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    Base b = Derived;
    b.methodeClasseDerivee;

    Par contre celui-ci va compiler.

    Code cpp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    Base* b = new Derived;
    b->methodeClasseDerivee;

    Voila pourquoi je ne peut pas faire d'allocation statique.

    La seule fois ou je peux le faire c'est en utilisant une référence sur une variable globale lors du linkage mais, ça n'est pas propre.

  4. #24
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 963
    Points
    32 963
    Billets dans le blog
    4
    Par défaut
    Ca n'empêche pas que
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    Derived b;
    MaCollection.Add(&b);
    sera possible, correct, et génèrera un joli crash bien plus tard quand on essayera d'accéder au pointeur qu'on a stocké. Ou pire.

    Le polymorphisme n'a rien à voir avec l'allocation dynamique en soi.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  5. #25
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 518
    Points
    41 518
    Par défaut
    Citation Envoyé par Iradrille Voir le message
    std::unique_ptr :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    int main() {
    	auto e = std::make_unique<EntityA>();
    	{
    		CellMap cm;
    		cm.addEntity(e);
    		// Je ne suis plus propriétaire de e, cm se démerde avec
    		// e n'est plus valide ici
    	}
    	// cm à été détruit, e aussi, à moins qu'il est été "donné" à un autre objet
    	// mais c'est pas mon problème, c'est celui du dev du framework
     
    	return 0;
    }
    Ne faut-il pas un std::move ici?
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  6. #26
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    393
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 393
    Points : 685
    Points
    685
    Par défaut
    Si (e est une lvalue, elle sera donc copiée si on utilise pas std::move)

  7. #27
    Invité
    Invité(e)
    Par défaut
    Ca n'empêche pas que
    Derived b;
    MaCollection.Add(&b);

    sera possible, correct, et génèrera un joli crash bien plus tard quand on essayera d'accéder au pointeur qu'on a stocké. Ou pire.

    Le polymorphisme n'a rien à voir avec l'allocation dynamique en soi.
    Oui je sais, c'est pas ça que je voulais dire, bref...

    avec le std::move ce code ne devrait pas crasher :

    Code cpp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    int main() {
        Derived derived;
        addEntity(&derived);
    }
    void addEntity(Derived* derived) {
       myvector.push_back(std::move(derived));
    }

    Vu que std::move déplace le contenu de la variable derived dans le vecteur. (Si j'ai bien compris)

    Donc dans ce cas-ci, l'adresse de la variable, et donc, c'est l’élément dans le vecteur qui prend possession de l'objet.

  8. #28
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    393
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 393
    Points : 685
    Points
    685
    Par défaut
    Citation Envoyé par Lolilolight Voir le message
    Vu que std::move déplace le contenu de la variable derived dans le vecteur. (Si j'ai bien compris)

    Donc dans ce cas-ci, l'adresse de la variable, et donc, c'est l’élément dans le vecteur qui prend possession de l'objet.
    Toujours pas, le problème persiste (si tu utilises un vector<unique_ptr<Entity>>)

  9. #29
    Membre expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    739
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

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

    Informations forums :
    Inscription : Juin 2011
    Messages : 739
    Points : 3 627
    Points
    3 627
    Par défaut
    Je vois surtout que tu ne comprends rien à la sémantique de mouvement.

    Pour commencer, std::move sur un pointeur ne sert strictement à rien, car il n'y a pas de constructeur de mouvement sur les types primitifs.
    Secondo, en supposant qu'il y est un constructeur de mouvement, l'extérieur ne verrait pas la différence, le paramètre est une copie. Tu peux faire tous et n'importe quoi avec une copie, l'original n'en à rien à carrer.
    Et puis tant qu'à faire, tu as déjà modifié l’adresse d'une variable sur pile ? &derived = 0; ? C'est exactement comme ça que tu présentes le code (du moins, comme ça qui, pour toi, devrait fonctionner...).

  10. #30
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    @Médinoc, oui un std::move est manquant, j'ai encore du mal avec lvalue / rvalue, mais en y réfléchissant un peu ça à du sens de le rajouter : ça permet d'exprimer le fait qu'on "abandonne" l'objet.

    Citation Envoyé par Lolilolight Voir le message
    Vu que std::move déplace le contenu de la variable derived dans le vecteur. (Si j'ai bien compris)

    Donc dans ce cas-ci, l'adresse de la variable, et donc, c'est l’élément dans le vecteur qui prend possession de l'objet.
    Le problème c'est qu'un pointeur c'est un type primitif, comme un int. Les types primitifs ne peuvent pas être bougés.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    int i = 42;
    int j = std::move(i);
     
    // equivalent à 
    int j = i;
    Donc au final tu ne fais rien d'autre qu'une copie du pointeur.

    Jettes un oeil au binaire généré pour t'en persuader si besoin
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <memory> // pour std::move
     
    int main() {
    	int i = 42;
     
    	int j = std::move(i);
    	int k = i;
     
    	return 0;
    }
    main commence par un entête qui n'a aucun intérêt ici.

    Pour la partie intéressante
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
         4: 	int i = 42;
    01281438  mov         dword ptr [i],2Ah  // i = 42, ça donne un simple mov, rien de spécial ici (2Ah == 42)
         5: 
         6: 	int j = std::move(i);
    0128143F  lea         eax,[i]  // recupération de l'adresse de i
    01281442  push        eax  // param de std::move
    01281443  call        std::move<int &> (012810EBh)  // appel de std::move(i), valeur de retour dans eax
    01281448  add         esp,4  // nettoyage de la pile ?
    0128144B  mov         ecx,dword ptr [eax]  // ecx = *eax (déréférencement de eax)
    0128144D  mov         dword ptr [j],ecx  // et copie de la valeur dans j
         7: 	int k = i;
    01281450  mov         eax,dword ptr [i]  // eax = *i (déréférencement de i; i représente l'adresse d'un int)
    01281453  mov         dword ptr [k],eax  // et copie dans k
    La copie est faite exactement de la même façon dans les 2 cas.

    Maintenant std::move, en C++ la fonction ressemble à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    template <class T>
    T&& move(T&& val) { // aucune idée du type réel du paramètre *_*
       return static_cast<T&&>(val);
    }
    Ce n'est au final qu'un cast, std::move ne fait rien à part autoriser la sémantique de mouvement en convertissant le paramètre en rvalue.

    Si on regarde le binaire généré, on s’aperçoit qu'effectivement std::move ne fait rien
    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
      1786: 		// TEMPLATE FUNCTION move
      1787: template<class _Ty> inline
      1788: 	typename remove_reference<_Ty>::type&&
      1789: 		move(_Ty&& _Arg) _NOEXCEPT
      1790: 	{	// forward _Arg as movable
    012813D0  push        ebp  
    012813D1  mov         ebp,esp  
    012813D3  sub         esp,0C0h  
    012813D9  push        ebx  
    012813DA  push        esi  
    012813DB  push        edi  
    012813DC  lea         edi,[ebp-0C0h]  
    012813E2  mov         ecx,30h  
    012813E7  mov         eax,0CCCCCCCCh  
    012813EC  rep stos    dword ptr es:[edi]  
      1791: 	return ((typename remove_reference<_Ty>::type&&)_Arg);
    012813EE  mov         eax,dword ptr [_Arg]  
      1792: 	}
    012813F1  pop         edi  
    012813F2  pop         esi  
    012813F3  pop         ebx  
      1792: 	}
    012813F4  mov         esp,ebp  
    012813F6  pop         ebp  
    012813F7  ret
    Beaucoup de "bordel" pour pas grand chose, les seuls trucs intéressants sont
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    012813EE  mov         eax,dword ptr [_Arg]  // le paramètre est un int*, on le récupère sur la pile et le place dans eax
    012813F7  ret  // == return eax
    Autrement dit std::move<int> est équivalent à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    int *move(int* val) {
       return val;
    }
    Aucun déplacement donc : std::move n'a aucun effet sur les types primitifs.

    A quoi ça nous mène tout ça ?
    C'est simple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Entity *e = new EntityA;
    CellMap cm;
    cm.addEntity(e); // addEntity peut utiliser std::move en interne ça ne change rien
    // e est et reste valide, il n'y a pas de mouvement, simplement une copie (le pointeur est copié, pas l'objet pointé)
    // de plus en appelant "cm.addEntity(e);" tu fourni déjà une copie de e
    // donc même si derrière e est bougé (et il ne l'est pas), ça ne change rien au problème
    // car tu bougerais une copie
     
    cm.addEntity(std::move(e)); // aurait plus de sens car on essayerait de bouger e et pas une copie de e
    // mais on se retrouve toujours face au même problème : e est un pointeur, et les pointeurs ne peuvent pas être bougés.
     
    delete e; // oups

  11. #31
    Invité
    Invité(e)
    Par défaut
    C'est faux.

    std::move ne fait pas de copie, mais déplace le contenu d'une variable dans une autre variable et rend le contenu de la première variable invalide.

    Un mécanisme similaire à std::swap mais à part que le contenu de la seconde variable est invalide. (Ce qui évite de devoir faire une copie, et donc une réallocation et un delete qui baisse les performances.)

    En assembleur cela donnerai une seule ligne de code :

    move adr[i], mvadr[i]

    Donc move déplace l'adresse de i (contenue dans la variable adr[i]) et la copie dans la variable mvadr[i], la variable adr[i] devient donc invalide.

    Par contre pour les types primitifs, comme il n'y a pas de move constructor, ça fait une copie oui.

  12. #32
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    393
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 393
    Points : 685
    Points
    685
    Par défaut
    Ne dis pas "c'est faux" alors que Iradrille montre le code assembleur génère (et que vous dites au final la même chose).

    Par contre, un précision sur move : cela ne fait pas un déplacement, cela autorise le déplacement. ie on indique que la value ne sera plus utilisée par le suite et que le compilateur peut faire des optimisations qui ne conservent pas la valeur de départ. Mais ensuite, le compilateur est libre de faire ce qu'il pense etre le plus efficace (copie ou déplacement).
    (Et plus précisément, un std::move ne fait rien. C'est juste un cast qui convertie en rvalue)

    (HS : je ne suis pas spécialiste de l'assembleur sur toutes les plateformes, mais une recherche rapide sur google me dit que move = copie. En assembleur, il n'y a pas de "déplacement", c'est une notion liée a la gestion des rvalue par un compilateur)

  13. #33
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 518
    Points
    41 518
    Par défaut
    Citation Envoyé par mintho carmo Voir le message
    C'est juste un cast qui convertie en rvalue
    Plus précisément, ça convertit en rvalue reference, le type de référence dont ont besoin les constructeurs de déplacement.

    Mais pour un type qui n'a pas de constructeur de déplacement de toute façon (comme les types primitifs: int, pointeur nu...), c'est une bête copie qui finira par être faite.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

Discussions similaires

  1. Erreur de compilation après modification du Uses
    Par DevelOpeR13 dans le forum Langage
    Réponses: 5
    Dernier message: 30/10/2007, 14h23
  2. Réponses: 2
    Dernier message: 23/09/2003, 14h32
  3. Réponses: 10
    Dernier message: 22/09/2003, 21h58
  4. Réponses: 4
    Dernier message: 27/08/2003, 21h34
  5. Réponses: 2
    Dernier message: 04/03/2003, 23h24

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