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 :

Fonction move() et move constructeur


Sujet :

Langage C++

  1. #1
    Nouveau membre du Club
    Profil pro
    Inscrit en
    Avril 2008
    Messages
    63
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2008
    Messages : 63
    Points : 32
    Points
    32
    Par défaut Fonction move() et move constructeur
    Bonjour !

    J'ai vraiment du mal à comprendre la fonction move ou le fonctionnement du move constructor.

    Je sais qu'elle peut faire plusieurs choses, typiquement utiliser une lvalue comme s'il s'agissait d'une rvalue.

    Mais à la base, son but est de déplacer un objet ou de le réaffecter. En tout cas d'éviter une copie.

    Or si j'ai une bête classe Animal et que j'écris ceci

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
            Animal rob("Robert", "belier", 7);
     
            Animal rob2 = std::move(rob);
     
            rob2.setAge(22);
     
            rob2.saluer();
     
            rob.saluer();
    Et bien j'ai deux objets distincts, comme si il y avait eu une copie !



    Est ce que quelqu'un peut m'éclairer s'il vous plait ?

  2. #2
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    std::move ne va rien bouger par elle même. Elle va permettre à d'autres fonctions de bouger.

    Imaginons que ta classe possède deux constructeurs :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class Animal
    {
      Animal (Animal const &a); // 1
      Animal (Animal &&a) noexcept; // 2
      //...
    };
    Sans le std::move, la création de rob2 se ferait avec le constructeur 1. Et on sait que ce dernier ne modifiera pas a (donc rob).
    Avec le std::move, la création de rob2 se fera avec le constructeur 2. Or, ce dernier a la possibilité (mais pas l'obligation) de modifier a (et donc rob). Mais si dans ton code, tu ne saisis pas cette opportunité pour par exemple voler les chaînes à ton objet rob afin d'aller plus vite, et bien tu as en effet juste créé deux copies.

    Après, on ne voit pas le code de ta classe, donc il faut faire des hypothèses sur ce que tu as pu écrire. Si tu as explicité les deux constructeurs comme moi, et que tu implémentes le second ainsi :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    Animal::Animal(Animal &&a) : age(std::move(a.age)), nom(std::move(a.nom)), race(std::move(a.race))
    {}
    Tu verras que rob n'est plus valide après la construction de rob2.

    Si tu n'as explicité aucun de ces constructeurs, que ta classe n'a pas de destructeurs, que tes données membres sont gentilles et bien intentionnées... (ce qui devrait être le cas pour la grande majorité de tes classes) alors le compilateur aura généré ces deux constructeurs pour toi, et le second ressemblera à ce que j'ai écrit, et rob n'est plus valide non plus après la création de rob2.

    Si tu as explicité le constructeur 1, ou le destructeur de ta classe, ou si tu es dans un des autres cas, le compilateur n'a pas le droit de générer le constructeur 2 pour toi. Tu n'as alors que la constructeur 1 à ta disposition, et la construction de rob2 utilisera alors ce constructeur, et laissera rob intact, même si tu est passé par std::move.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  3. #3
    Nouveau membre du Club
    Profil pro
    Inscrit en
    Avril 2008
    Messages
    63
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2008
    Messages : 63
    Points : 32
    Points
    32
    Par défaut
    Merci beaucoup pour ta réponse =)

    Alors j'ai implémenté ton constructeur de mouvement et j'ai toujours les deux objets qui fonctionnent, donc je te poste mon code

    Mais en gros j'ai juste un constructeur de copie d'implémenté et tu avais deviné mes trois attributs.

    Animal.h
    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
        class Animal
        {
     
            protected:
     
            std::string m_nom;
            std::string m_espece;
            int m_age;
     
            public:
     
            Animal(std::string nom, std::string espece, int age);
            Animal(Animal const& modeleAnimal);
            Animal(Animal &&a);
     
            virtual void saluer();
     
            int getAge();
            void setAge(int age);
        };
    Animal.cpp
    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
        Animal::Animal(string nom, string espece, int age) :
            m_nom(nom),m_espece(espece),m_age(age)
        {
        }
     
        Animal::Animal(Animal const& modeleAnimal) :
            m_nom(modeleAnimal.m_nom), m_espece(modeleAnimal.m_espece),
            m_age(modeleAnimal.m_age)
        { cout << "Constructeur de copie appele !" << endl; }
     
        Animal::Animal(Animal &&a) : m_age(std::move(a.m_age)), m_nom(std::move(a.m_nom)), m_espece(std::move(a.m_espece))
        {}
     
        void Animal::saluer()
        {
            cout << "Bonjour ! Je suis " << m_nom <<endl;
            cout << "Je suis un " << m_espece << " et j'ai ";
            cout << m_age << " ans.";
        }
     
        int Animal::getAge()
        {
            return m_age;
        }
     
        void Animal::setAge(int age)
        {
            if(age >= 0)
                m_age = age;
            else
                cout << "ERREUR ! Age doit etre positif !";
        }
    main.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
            Animal rob("Robert", "belier", 7);
     
            Animal rob2 = std::move(rob);
     
            rob2.setAge(22);
     
            rob2.saluer();
     
            rob.saluer();
    Et à la fin j'ai bien rob2 qui a 22 ans et rob qui a 7 an, comme si c'était une copie.
    Pourtant mon constructeur de copie n'est pas appelé

  4. #4
    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 965
    Points
    32 965
    Billets dans le blog
    4
    Par défaut
    tu construits rob2 non pas avec le constructeur de mouvement mais l'assignation par mouvement. operator=(Bidule&&)
    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. #5
    Membre chevronné Avatar de Ehonn
    Homme Profil pro
    Étudiant
    Inscrit en
    Février 2012
    Messages
    788
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2012
    Messages : 788
    Points : 2 160
    Points
    2 160
    Par défaut
    Citation Envoyé par Bousk Voir le message
    tu construits rob2 non pas avec le constructeur de mouvement mais l'assignation par mouvement. operator=(Bidule&&)
    Malgré la syntaxe, c'est le constructeur qui est appelé :
    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
    // g++ -Wall -Wextra -Wconversion -Wsign-conversion -Ofast -std=c++14 -pedantic -fopenmp main.cpp -o main && ./main
     
    #include <iostream>
     
    struct test_t
    {
    	test_t() { std::cout << "test_t()" << std::endl; }
     
    	test_t(test_t const &) { std::cout << "test_t(test_t const &)" << std::endl; }
     
    	test_t(test_t &&) { std::cout << "test_t(test_t &&)" << std::endl; }
     
    	test_t & operator =(test_t const &) { std::cout << "operator =(test_t const &)" << std::endl; return *this; }
     
    	test_t & operator =(test_t &&) { std::cout << "operator =(test_t &&)" << std::endl; return *this; }
    };
     
    int main()
    {
    	test_t t0; // test_t()
     
    	test_t t1 = std::move(t0); // test_t(test_t &&)
     
    	return 0;
    }
    @Nix6800>
    Généralement, après avoir déplacé une variable, on ne l'utilise plus (car elle peut être dans un état "invalide").

  6. #6
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Ton int n'a pas changé, car déplacer un int revient à le copier, il n'y a aucune astuce qui permette d'aller plus vite, et toute autre option rendrait le programme plus lent. Il ne faut pas oublier que le mot déplacement est trompeur : Il n'y a jamais déplacement. Il y a toujours une nouvel emplacement mémoire dont le contenu sera équivalent à celui de la source. La seule différence, c'est que dans un cas la copie laisse l'original inchangé, dans l'autre elle a le droit de changer l'original, et elle le fera si ça lui permet d'aller plus vite.
    Et par exemple, pour les strings, il y a moyen d'aller plus vite. Par exemple, avec mon implémentation, celles de rob sont devenues vides.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    Bonjour ! Je suis Robert
    Je suis un belier et j'ai 22 ans.Bonjour ! Je suis
    Je suis un  et j'ai 7 ans.
    Le comportement exact n'est pas défini (la norme précise pour std::string "str is left in a valid state with an unspecified value."). En pratique, les deux comportements que je pense on va souvent trouver sont
    - soit la source du déplacement est toujours vide après déplacement,
    - soit la source est identique à ce qu'elle était pour les petites chaînes, et vidée pour les grandes (ce qui peut avoir de l'intérêt pour les chaînes implémentant la small string optimisation).

    En pratique, quand un objet a été moved-from, il n'y a guère que deux opérations que tu peux effectuer : Le détruire, ou lui assigner une nouvelle valeur. Certains objets peuvent promettre plus, mais c'est rarement utile. Certains objets peuvent promettre moins, mais c'est ultra casse-gueule, et de tels objets n'auraient pas le droit d'être utilisés avec la bibliothèque standard...
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  7. #7
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Citation Envoyé par Bousk Voir le message
    tu construits rob2 non pas avec le constructeur de mouvement mais l'assignation par mouvement. operator=(Bidule&&)
    Quand tu construits un objet, c'est forcément un constructeur qui est appelé, même si la syntaxe fait apparaître le caractère '='. L'assignation par mouvement serait appelée quand on assigne dans un objet préalablement construit.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    Animal rob("Robert", "belier", 7);
    Animal rob2("Dolly", "brebis", 5);
    rob2 = std::move(rob);
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  8. #8
    Membre émérite
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 764
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 764
    Points : 2 704
    Points
    2 704
    Par défaut
    En fait, j'ai réellement compris la sémantique de mouvement quand j'ai assimilé les r-value references (&&) à un qualificateur permettant de résoudre les surcharges de fonction, comme le fait le qualificateur const.

    std::move() n'est rien d'autre qu'un static_cast<> qui va ajouter le qualificateur &&, ce qui permettra de choisir foo(int&&) plutôt que foo(int&).
    Tu peux faire exactement le même raisonnement avec un const : si tu appliques un const_cast<>, tu vas appeler bar(const int&) plutôt que bar(in&).

    C'est pareil avec les fonctions membres d'un objet : tu peux qualifier une fonction & ou && comme tu le ferais avec const :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class MaClasse
    {
        int foo1() const;
        int foo2() &;
        int foo3() &&;
    };
    foo1 ne sera appelable que sur des objets const, foo2 sur des rvalue-references, et foo3 sur des rvalue-references.

    On assimile souvent std::move() à un vol de ressource (honteusement induits en erreur par le nom de la fonction, il est vrai), alors que cela ne fait que te donner une autorisation de voler toi-même cette ressource.
    Et encore, c'est purement une convention : rien ne t'oblige à commettre le larcin dans ton constructeur (mais bon, tu annonces le contraire par la sémantique).

    Dans le cas d'un bête entier, ça ne change pas grand chose.
    Mais par exemple, dans le cas d'un vecteur, ça a un intérêt : le constructeur par copie & mouvement se contentera de faire une copie superficielle, en mettant le vecteur source dans un valide mais non spécifié (mais pour une classe quelconque, rien n'impose que sont état soit valide après un move).
    Pour éviter tout ennui, considère pour l'instant que tu ne dois jamais utiliser un objet qui a subi un std::move(). C'est un objet potentiellement vampirisé auquel on a pu pomper quelques litres de sang. Ce n'est probablement plus un compagnon de route très vaillant.

Discussions similaires

  1. Fonction rand dans un constructeur
    Par tidusff10 dans le forum Débuter
    Réponses: 2
    Dernier message: 13/01/2013, 12h18
  2. Réponses: 6
    Dernier message: 06/02/2007, 13h03
  3. demande info de la fonction move dans un module
    Par lechtifred dans le forum Access
    Réponses: 1
    Dernier message: 01/05/2006, 15h04
  4. Réponses: 3
    Dernier message: 06/11/2005, 18h02

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