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 :

Pointeur intelligent à usage purement interne


Sujet :

Langage C++

  1. #1
    Membre émérite Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Par défaut Pointeur intelligent à usage purement interne
    Bonjour,
    Suite à cette discussion, je me pose des questions sur l'intérêt de wrapper des ressources à usage purement interne (à condition de les gérer proprement et correctement).
    Ne serait-ce pas essayer de tuer une mouche avec un bazooka ? ou tout du moins, un fusil d'assaut ?

    Prenons une classe qui contient un pointeur en tant que donnée membre privée, sachant que les données pointées sont complètement gérées par la classe.
    Dans les codes postés, je ne m'intéresse qu'audit pointeur.
    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
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    class une_classe
    {
     
        typedef ... un_type;
     
     
        un_type *m_pointeur;
     
     
      public:
        une_classe() : m_pointeur()
        {
            try {
                m_pointeur = new un_type(...);
                (...)
            }
            catch (std::exception const&) {
                reset();
                (...)
                throw;
            }
        }
     
        une_classe(...) : m_pointeur()
        {
            try {
                m_pointeur = new un_type(...);
                (...)
            }
            catch (std::exception const&) {
                reset();
                (...)
                throw;
            }
        }
     
        une_classe(une_classe const&) = delete;
     
        une_classe(une_classe&& autre) noexcept : m_pointeur(autre.m_pointeur)
        {
            autre.m_pointeur = nullptr;
        }
     
     
        ~une_classe()
        {
            reset();
        }
     
     
        une_classe& operator = (une_classe const&) = delete;
     
        une_classe& operator = (une_classe&& autre) noexcept
        {
            un_type *tmp = autre.m_pointeur;
            autre.m_pointeur = nullptr;
            m_pointeur = tmp;
            return *this;
        }
     
     
      private:
        void set(un_type *ptr) noexcept
        {
            delete m_pointeur;
            m_pointeur = ptr;
        }
     
        void reset() noexcept
        {
            return set(nullptr);
        }
     
    }; // class une_classe
    Ici, tous les constructeurs allouent de la mémoire pour le pointeur, mais si dans certains cas on n'allouait pas de mémoire, le problème serait le même.
    L'allocation a lieu dans le corps du constructeur pour être sûr que toutes les autres données membres ont été construites, et pour le cas où certains paramètres du constructeur de un_type nécessiteraient certains calculs.
    Bien entendu, si une exception survient dans le constructeur après l'allocation, on pensera à libérer la mémoire allouée.

    Pas de constructeur par copie (ni d'opérateur d'affection).
    Qui serait chargé de libérer la mémoire ?

    Si je ne me trompe pas, le déplacement n'est pas possible pour les types POD.
    Il faut donc remettre à zéro le pointeur membre de l'objet que l'on veut déplacer.

    Le destructeur se charge de libérer la mémoire allouée pour le pointeur, si tel a été le cas.

    Pour l'opérateur d'affectation par déplacement, passer par une variable auxiliaire me permet d'éviter l'écueil de l'auto-affectation.
    Quant à l'échange, l'implémentation par défaut utilise le constructeur par déplacement et l'opérateur d'affectation par déplacement.
    Donc pas besoin d'y toucher.

    C'est sûr, j'aurais pu utiliser l'idiome « move-and-swap » :
    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
    une_classe& une_classe::operator = (une_classe&& autre) noexcept
    {
        une_classe tmp = std::move(autre);
        swap(tmp);
        return *this;
    }
     
     
    void une_classe::swap(une_classe& autre)
    {
        std::swap(m_pointeur, autre.m_pointeur);
    }
     
     
    namespace std
    {
     
        void swap(une_classe& x, une_classe& y)
        {
            return x.swap(y);
        }
     
    } // namespace std
    S'il y a plusieurs données membres qui ne peuvent pas être déplacées simplement, ça simplifie/clarifie le code...
    Au fait, pour swap(x, y), il vaut mieux la surcharger ou la spécialiser ?

    Voilà.
    m_pointeur n'est jamais exposé à l'extérieur.
    Les données pointées peuvent éventuellement être exposées, mais en tant que référence (éventuellement constante).
    Si chaque modification de la valeur du pointeur se fait via set ou reset, il ne devrait pas y avoir de problème de ressources non libérées, ou de ressources libérées plusieurs fois.

    Jusque là, ça vous va ?
    Je n'ai rien oublié ?
    Je pars du principe que cette classe n'a pas d'ami (la pauvre... ), ou que ses amis ont la décence de ne pas toucher (directement) aux données membres.


    Bien.
    Intéressons-nous maintenant à la même chose implémentée à l'aide d'un pointeur intelligent adéquat.
    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
    class une_classe
    {
     
        typedef ... un_type;
     
     
        std::unique_ptr<un_type> m_pointeur;
     
     
      public:
        une_classe() : m_pointeur(new un_type(...))
        {}
     
        une_classe(...) : m_pointeur()
        {
            m_pointeur.reset(new un_type(...));
        }
     
        une_classe(une_classe const&) = delete;
     
        une_classe(une_classe&&) = default;
     
     
        ~une_classe() = default;
     
     
        une_classe& operator = (une_classe const&) = delete;
     
        une_classe& operator = (une_classe&&) = default;
     
    }; // class une_classe
    C'est sûr, c'est plus concis...

    Mais pour autant, est-ce que cela apporte quelque chose au code ?
    Ça le rend plus clair ? plus lisible ?
    Est-ce que ça ne va pas trop faire grossir la structure de données, alourdir l'application ?
    Honnêtement, je n'en sais rien.
    Il y a certes moins de lignes, mais au moins dans la première version on sait ce que l'on fait.
    Et puis dans la seconde, on rajoute des dépendances.
    Et vous, qu'en pensez-vous ?


    Hum...
    Je viens de penser à un truc.
    Le « principe de responsabilité unique ».
    Est-ce que par hasard utiliser un pointeur intelligent dans ce cas ne permettrait-il pas de respecter ce principe (en tout cas, d'y tendre), alors que sans, il serait violé ?

  2. #2
    Membre émérite Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Par défaut
    Wow...
    Près de 100 visionnages, et pas un seule réponse...
    Je ne sais pas trop comment je dois interpréter cela...

    Mon interrogation est-elle tellement stupide que personne ne veut prendre la responsabilité de me le faire savoir ?
    La question a-t-elle été posée si souvent que plus personne ne veut prendre la peine d'y répondre ?
    Ai-je poussé la réflexion trop/pas assez loin, et personne ne sait par quel bout la prendre ?
    Peut-être raconte-je n'importe quoi, et alors personne ne voit où je veux en venir ?
    La question pose-t-elle un problème si complexe que personne n'a de réponse (suffisamment simple) à donner ?
    À moins que je ne donne moi-même la réponse à ma propre question ?

    Là, franchement, je suis perdu...
    J'aimerais beaucoup que vous m'éclairiez...

  3. #3
    Membre Expert

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    Sans détailler, refait le même raisonnement avec deux données allouées dynamiquement (*) et en supposant que leur constructeur respectif peut lancer une exception (**), tu devrais voir assez rapidement l'interet primordial du RAII pour les ressources même interne (ici std::unique_ptr/boost::scoped_ptr).

    Edit: Pour swap, c'est une spécialisation, tu n'as pas le droit "d'ajouter" quelque chose dans le namespace std, donc tu ne dois pas ajouter une surchager de std::swap, par contre tu peux le spécialiser.

    Si c'est une classe template la fonction libre swap template ne peut pas être une spécialisation de std::swap, dans ce cas tu la mets dans le namespace de ta classe, grace à l'ADL ca marchera.

    Edit2: Pour ton opérateur "move-assign" c'est inutile de créer un temporaire, on est pas dans le cas d'un "copy-assign" où l'original ne doit pas être modifié, là on sait même que "l'original" est "sur le point de mourrir" (c'est une facon de voir la signification de &&), ainsi il suffit de swap *this avec cette valeur (au sein de la fonction c'est une lvalue, donc on peut construire une lref à partir de celle ci), ie
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    A& operator=(A&& rhs)
    { std::swap(*this, rhs); return *this; }
    (***)

    Et une petite remarque, inutile de mettre delete les copy-ctor et copy-assign si tu déclares explicitement move-ctor/move-assign, ils le sont implictement.

    (*) Dans la liste d'initialisation normalement, l'interet sera d'autant plus flagrant.

    (**) Pour raisonner suppose que le premier constructeur passe et que le second lance, à toi de conclure.

    (***) Version plus complète si tu veux tester :
    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
     
    #include<utility>
    #include<iostream>
     
    struct B
    {
    	static int i;
    	int j;
    	B() : j(i++) { std::cout << " ctor B " << j; }
    	~B() { std::cout << " dtor B " << j; }
    };
     
    int B::i = 0;
     
    struct A
    {
    	static int i;
    	int j;
    	B* p;
    	A();
    	~A();
    	void swap(A&);
    	A(A&& a);
    	A& operator=(A&&);
    };
     
    int A::i = 0;
     
    namespace std
    {
    	template<>
    	void swap(A& lhs, A& rhs)
    	{ lhs.swap(rhs); }
    }
     
    A::A() : j(i++), p(new B())
    { std::cout << " ctor A " << j; }
    A::~A()
    { delete p; std::cout << " dtor A " << j; }
    void A::swap(A& rhs)
    { std::swap(p,rhs.p); }
    A::A(A&& a) : j(i++), p(a.p) 
    { a.p = nullptr; std::cout << " move-ctor A " << j; }
    A& A::operator=(A&& rhs)
    { std::swap(*this,rhs);
      std::cout << " move-assign A " << j;
      return *this; }
     
    A foo()
    { return A(); }
     
    int main()
    {
    	A a;
    	std::cout << " _ ";
    	a = foo();
    	std::cout << " _ ";
    }
    Sortie : ctor B 0 ctor A 0 _ ctor B 1 ctor A 1 move-assign A 0 dtor B 0 dtor A 1 _ dtor B 1 dtor A 0
    On remarque que le temporaire créé par foo est bien détruit (ctor A 1 et dtor A 1 entre les deux _), mais que la ressource utilisé a bien été "swappé" (ctor B 0 avant l'appel, ctor B 1 et dtor B 0 entre les deux _, et enfin dtor B 1 )

    PS: Si tu veux pas faire le raisonnement, Sutter doit le faire dans un article de GotW (ou dans la série de livre qu'il a écrit adapté des GotW).

  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
    Citation Envoyé par Steph_ng8
    Mon interrogation est-elle tellement stupide que personne ne veut prendre la responsabilité de me le faire savoir ?
    Ben disons qu'en voyant ça...

    Citation Envoyé par Steph_ng8
    Mais pour autant, est-ce que cela apporte quelque chose au code ?
    Ça le rend plus clair ? plus lisible ?
    Le code 1 est long, difficile, parsemé de try/catch illisible, et est probablement buggé. (car faire une classe gérant correctement n'est pas si simple sans RAII) (voir [1])
    Le code 2 avec unique_ptr est dix fois plus court, extrêmement simple et correct.
    Donc ça me dépasse complètement que tu ne vois pas ce que ça apporte au code.

    Pour ce qui est des autres questions :
    Citation Envoyé par Steph_ng8
    Est-ce que ça ne va pas trop faire grossir la structure de données, alourdir l'application ?
    Un unique_ptr c'est un wrapper sur un pointeur nu, c'est à dire que c'est une structure possédant un seul membre qui est un pointeur. Donc il n'y a pas de raison que la taille soit différente.
    Citation Envoyé par Steph_ng8
    Il y a certes moins de lignes, mais au moins dans la première version on sait ce que l'on fait.
    Ah ben c'est plutôt le contraire pour ma part ! La seconde version utilise un composant de la STL, donc je vois très bien ce que ça fait vu que le comportement est standard, alors que la premier code est un méli mélo perso, donc il me faut beaucoup plus de temps pour savoir ce que ça fait. Et aussi je pars du principe qu'il n'y a pas de bug dans std::unique_ptr alors que dans l'autre cas...
    Citation Envoyé par Steph_ng8
    Et puis dans la seconde, on rajoute des dépendances.
    std::unique_ptr est définit dans un header de la STL, très probablement de manière header-only, donc la dépendance est quand même très légère. Par contre c'est vrai qu'il n'est pas présent sur les anciens compilos. Et ça c'est un réel désavantage à garder en tête (peut être le seul).


    [1]
    Un exemple de bug assez vicieux dans ton code qui aurait pu ne pas être détecté avant un sacré bout de temps en prod :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    une_classe& operator = (une_classe&& autre) noexcept
    {
       un_type *tmp = autre.m_pointeur;
       autre.m_pointeur = nullptr;
       m_pointeur = tmp;
       return *this;
    }
    Dans l'op = il faut commencer par libérer la ressource existante, c'est à dire faire un delete sur m_pointeur sinon il y a une fuite mémoire.

  5. #5
    Membre Expert
    Avatar de Joel F
    Homme Profil pro
    Chercheur en informatique
    Inscrit en
    Septembre 2002
    Messages
    918
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur en informatique
    Secteur : Service public

    Informations forums :
    Inscription : Septembre 2002
    Messages : 918
    Par défaut
    Citation Envoyé par Arzar Voir le message
    Un exemple de bug assez vicieux dans ton code qui aurait pu ne pas être détecté avant un sacré bout de temps en prod :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    une_classe& operator = (une_classe&& autre) noexcept
    {
       un_type *tmp = autre.m_pointeur;
       autre.m_pointeur = nullptr;
       m_pointeur = tmp;
       return *this;
    }
    Dans l'op = il faut commencer par libérer la ressource existante, c'est à dire faire un delete sur m_pointeur sinon il y a une fuite mémoire.
    Non, c'ets un move-assignment, le pointeur sera detruit par la liberation du temporaire référencée par la rvalue-ref autre.

  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 Joel F Voir le message
    Non, c'ets un move-assignment, le pointeur sera detruit par la liberation du temporaire référencée par la rvalue-ref autre.
    Pas d'accord, dans le code de Steph_ng8 le pointeur de l'objet bindé à la rref est mis à null, donc on a bien un leak de la ressource détenu par l'objet qui va subir l'affectation et d'ailleurs dans le cas général il ne faut pas faire de swap dans un move-assignement car la rvalue-ref n'est pas forcement un temporaire !
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    une_classe u1;
    une classe u2;
    u2 = std::move(u1); 
    // il ne faut pas que u1 se retrouve avec les ressources d'u2 après l'affectation !
    Par exemple, un move-assignement correct pour un std::vector (tiré du site C++next)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    vector& operator=(vector&& rhs)
    { 
        if (this->begin_)
        {
            this->destroy_all(); // destroy all elements
            this->deallocate();  // deallocate memory buffer
        }
        this->begin_ = rhs.begin_;
        this->end_ = rhs.end_;
        this->cap_ = rhs.cap_;
        rhs.begin_ = rhs.end_ = rhs.cap_ = 0;
        return *this;
    }

  7. #7
    Membre émérite Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Par défaut
    Citation Envoyé par Flob90 Voir le message
    tu n'as pas le droit "d'ajouter" quelque chose dans le namespace std
    « Pas le droit » ?
    C'est une (très) forte recommandation, ou une réelle interdiction ?
    Plus sérieusement, je ne voyais pas une telle surcharge comme un véritable ajout.
    Même si techniquement, c'est en un, nous sommes d'accord.

    Citation Envoyé par Flob90 Voir le message
    dans ce cas tu la mets dans le namespace de ta classe, grace à l'ADL ca marchera.
    Ça je n'en doute pas.
    Sauf que ça ne marchera que si l'on n'appelle pas spécifiquement std::swap.
    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
    template <typename T>
    class A
    { (...) };
     
    template <typename T>
    void swap(A<T>& x, A<T>& y)
    { (...) }
     
     
    /*******************/
     
    #include <algorithm>
     
    void f()
    {
        A<int> o1, o2;
        std::swap(o1, o2);
    }
    (Supposons que le programmeur ne connaisse pas l'existence de la fonction spécifique.)
    Quelle fonction va être effectivement exécutée ?
    Pas la nôtre, en effet.
    Il faut préciser quelque part que les programmeurs de doivent pas appeler explicitement l'implémentation standard de swap, ou y a-t-il un moyen faire quelque chose d'uniforme pour tous les types ?


    Citation Envoyé par Flob90 Voir le message
    Pour ton opérateur "move-assign" c'est inutile de créer un temporaire, on est pas dans le cas d'un "copy-assign" où l'original ne doit pas être modifié, là on sait même que "l'original" est "sur le point de mourrir" (c'est une facon de voir la signification de &&), ainsi il suffit de swap *this avec cette valeur (au sein de la fonction c'est une lvalue, donc on peut construire une lref à partir de celle ci)
    Ah oui, je n'y avais pas réfléchi...


    Citation Envoyé par Flob90 Voir le message
    inutile de mettre delete les copy-ctor et copy-assign si tu déclares explicitement move-ctor/move-assign, ils le sont implictement.
    Ah, c'est bon à savoir.
    Alors quelle est la règle précise ?
    Lorsque l'on déclare explicitement le move-ctor (resp. move-assign), le copy-ctor (resp. copy-assign) est supprimé implicitement ?
    Ou il faut que les deux soit déclarés explicitement pour les deux autres soient supprimés ?
    Ou il en suffit d'un seul pour supprimer les deux ?

    D'après mes tests, ce serait plutôt la première version.
    Mais le plus « drôle », c'est que ça fonctionne aussi lorsque l'on supprime explicitement le move-ctor et/ou le move-assign !



    Citation Envoyé par Arzar Voir le message
    Citation Envoyé par Steph_ng8;
    Il y a certes moins de lignes, mais au moins dans la première version on sait ce que l'on fait.
    Ah ben c'est plutôt le contraire pour ma part ! La seconde version utilise un composant de la STL, donc je vois très bien ce que ça fait vu que le comportement est standard, alors que la premier code est un méli mélo perso, donc il me faut beaucoup plus de temps pour savoir ce que ça fait. Et aussi je pars du principe qu'il n'y a pas de bug dans std::unique_ptr alors que dans l'autre cas...
    Hum...
    À la réflexion, c'est vrai qu'on sait ce qu'on a écrit, mais ce n'est pas pour autant que ça va faire ce que l'on attend...



    Citation Envoyé par Joel F Voir le message
    Citation Envoyé par Arzar Voir le message
    Un exemple de bug assez vicieux dans ton code qui aurait pu ne pas être détecté avant un sacré bout de temps en prod :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    une_classe& operator = (une_classe&& autre) noexcept
    {
       un_type *tmp = autre.m_pointeur;
       autre.m_pointeur = nullptr;
       m_pointeur = tmp;
       return *this;
    }
    Dans l'op = il faut commencer par libérer la ressource existante, c'est à dire faire un delete sur m_pointeur sinon il y a une fuite mémoire.
    Non, c'ets un move-assignment, le pointeur sera detruit par la liberation du temporaire référencée par la rvalue-ref autre.
    Non, Arzar a raison.
    L'ancienne valeur de m_pointeur n'est pas passée à autre, et donc est perdue à jamais.
    Je me suis tellement focalisé sur l'auto-assignation que j'en ai oublié que la cas « normal »...
    Du coup, il n'y a que l'auto-assignation qui est bien gérée...


    Citation Envoyé par Arzar Voir le message
    Le code 1 est long, difficile, parsemé de try/catch illisible, et est probablement buggé. (car faire une classe gérant correctement n'est pas si simple sans RAII) (voir [1])
    Le code 2 avec unique_ptr est dix fois plus court, extrêmement simple et correct.
    Bon, je crois que je vais retenir ceci...


    Citation Envoyé par Arzar Voir le message
    Donc ça me dépasse complètement que tu ne vois pas ce que ça apporte au code.
    Ce n'est pas que je ne voyais pas ce que ça apporte au code, mais je me demandais si ce gain est vraiment significatif.
    (À présent j'en suis convaincu.)

    Comme quoi, poser des questions dont la réponse semble pourtant évidente permet d'obtenir le bon raisonnement et les bonnes justifications.
    Cela permet aussi de lever des doutes et d'être soi-même (pleinement) convaincu.
    Et puis ça permet également d'apprendre des petites choses sur des éléments par vraiment liés...

    Quoi qu'il en soit, merci de vos interventions !

    Ah, par contre, il reste juste le « problème » du swap...

  8. #8
    Membre émérite Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Par défaut
    Grilled...

    Citation Envoyé par Arzar Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    une_classe u1;
    une classe u2;
    u2 = std::move(u1); 
    // il ne faut pas que u1 se retrouve avec les ressources d'u2 après l'affectation !
    C'est exactement pour cette raison que j'affectais autre.m_pointeur à nullptr.

    Bon par contre, j'avais oublié de m'occuper des anciennes ressources...

  9. #9
    Membre Expert

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    @Arzar:
    Citation Envoyé par Arzar Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    une_classe u1;
    une classe u2;
    u2 = std::move(u1); 
    // il ne faut pas que u1 se retrouve avec les ressources d'u2 après l'affectation !
    Je ne comprends pas trop pourquoi, l'utilisateur a utilisé move, il devrait être totalement conscient que ce qu'il passe peut-être modifié ("déplacé"), et n'aura plus nécessairement de "sens". (on utilise move quand on est certain de ne plus utiliser la ressource normalement, pas avant je pense).
    Citation Envoyé par Arzar Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    vector& operator=(vector&& rhs)
    { 
        if (this->begin_)
        {
            this->destroy_all(); // destroy all elements
            this->deallocate();  // deallocate memory buffer
        }
        this->begin_ = rhs.begin_;
        this->end_ = rhs.end_;
        this->cap_ = rhs.cap_;
        rhs.begin_ = rhs.end_ = rhs.cap_ = 0;
        return *this;
    }
    En effet c'est ce qu'ils disent sur C++Next, mais je ne vois pas ce qu'on gagne par rapport à un simple swap, à part une "RAZ" de la ressource déplacée.

    @Steph_ng8:
    1\ C'est un réel interdiction il me sembe.
    2\ Utilises swap comme ca (Eff++, Meyers) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    //code
    using std::swap;
    swap(/*code*/);
    //code
    Ainsi la bonne version sera appelé dans tout les cas.
    3\ Pour les règles exact regardes dans un draft de la norme (l'idée de base est qu'à partir du moment où tu déclares explicitement une de ces 4 fonctions il faut que tu déclares toutes celles que tu veux "utiliser").

    PS: Pour 3\, il y a un diagramme sur un blog qui résume ces règles, mais je le trouve plus.

  10. #10
    Membre émérite Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Par défaut
    Citation Envoyé par Flob90 Voir le message
    Citation Envoyé par Arzar Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    une_classe u1;
    une classe u2;
    u2 = std::move(u1); 
    // il ne faut pas que u1 se retrouve avec les ressources d'u2 après l'affectation !
    Je ne comprends pas trop pourquoi, l'utilisateur a utilisé move, il devrait être totalement conscient que ce qu'il passe peut-être modifié ("déplacé"), et n'aura plus nécessairement de "sens". (on utilise move quand on est certain de ne plus utiliser la ressource normalement, pas avant je pense).
    En y réfléchissant, c'est vrai que ce n'est pas si gênant que u1 possède après l'affection les ressources que possédait u2 avant.
    Après tout, ces dernières ne sont plus censées être utilisées.
    Si la classe est bien faite (), elles seront libérées à la réaffectation de u1, ou à défaut à sa destruction.

    En enchaînant les move-assign, on peut se retrouver à différer la libération jusqu'à la fin du programme...
    Il faut juste se souvenir que les données forment alors un objet « non utilisable » mais « prêt à être détruit ».

    Je change le code de l'opérateur dans mon premier message...
    [Edit]
    En fait, non... je ne peux mas éditer le premier message...
    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
    une_classe& une_classe::operator = (une_classe&& autre)
    {
        un_type *tmp = m_pointeur;
        m_pointeur = autre.m_pointeur;
        autre.m_pointeur = tmp;
        return *this;
    }
     
    // Move-and-swap
     
    une_classe& une_classe::operator = (une_classe&& autre)
    {
        swap(autre);
        return *this;
    }
    Comme ça, ça fonctionne en cas d'auto affectation, et sinon c'est l'objet « déplacé » qui libérera les anciennes ressources.
    [/Edit]


    Citation Envoyé par Flob90 Voir le message
    2\ Utilises swap comme ca (Eff++, Meyers) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    //code
    using std::swap;
    swap(/*code*/);
    //code
    Ainsi la bonne version sera appelé dans tout les cas.
    Hum... mouais...
    Si c'est Meyers qui le dit...

    Ok pour le reste.

    Et encore une fois, merci.

  11. #11
    Membre Expert
    Avatar de Joel F
    Homme Profil pro
    Chercheur en informatique
    Inscrit en
    Septembre 2002
    Messages
    918
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur en informatique
    Secteur : Service public

    Informations forums :
    Inscription : Septembre 2002
    Messages : 918
    Par défaut
    Citation Envoyé par Arzar Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    une_classe u1;
    une classe u2;
    u2 = std::move(u1); 
    // il ne faut pas que u1 se retrouve avec les ressources d'u2 après l'affectation !
    Turlututu, si tu move u1 dans u2, u1 n'est plus valide.

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

Discussions similaires

  1. Problème avec les pointeurs intelligents de boost.
    Par Le Barde dans le forum Boost
    Réponses: 2
    Dernier message: 05/09/2007, 12h47
  2. delete [] et pointeur intelligent
    Par zenux dans le forum C++
    Réponses: 11
    Dernier message: 04/12/2006, 09h18
  3. Les pointeurs intelligents
    Par MatRem dans le forum C++
    Réponses: 8
    Dernier message: 20/06/2006, 19h27
  4. pointeur intelligent??
    Par yashiro dans le forum C++
    Réponses: 3
    Dernier message: 04/04/2006, 08h08
  5. Pointeur intelligent boost : return NULL ->comment faire?
    Par choinul dans le forum Bibliothèques
    Réponses: 7
    Dernier message: 21/12/2005, 16h24

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