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

C++ Discussion :

[Débat] Affectation de mouvement appelant le destructeur puis le constructeur de mouvement


Sujet :

C++

  1. #1
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 470
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 470
    Points : 6 107
    Points
    6 107
    Par défaut [Débat] Affectation de mouvement appelant le destructeur puis le constructeur de mouvement
    Bonjour,

    Dans le cas où le compilateur ne peut pas générer automatiquement le code de l'affectation de mouvement, peut-on considérer comme une bonne pratique de développement le fait de factoriser le code de l'affectation de mouvement en appelant explicitement le destructeur puis le constructeur de mouvement ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    Foo& operator=(Foo&& other) noexcept
    {
        if(this != &other) {   // allows std::swap<Foo>(x,x)
            this->Foo::~Foo(); // "Foo::" prevents virtuality if the destructor is virtual.
            new(this) Foo(std::move(other));
        }
        return *this;
    }
    Voici un exemple très concret où je trouve que cette pratique est pertinente.
    Admettons qu'une personne code un modèle de classe MovableRaiiFileStream<FileStreamType> avec un membre de type FileStreamType* et qui gère l'ouverture et la fermeture :
    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
    template<class FileStreamType>
    class MovableRaiiFileStream
    {
    private:
        FileStreamType* m_file;
     
        bool invariant()
        {
            return m_file != nullptr;
        }
    public:
        MovableRaiiFileStream(FileStreamType& file, const std::string& path) :
            m_file(&file)
        {
            file.open(path);
            if(!file.is_open())
                throw std::runtime_error("Unable to open file \"" + path + "\".");
        }
        MovableRaiiFileStream(const MovableRaiiFileStream& other)            = delete;
        MovableRaiiFileStream& operator=(const MovableRaiiFileStream& other) = delete;
        MovableRaiiFileStream(MovableRaiiFileStream&& other) noexcept :
            m_file(other.m_file)
        {
            other.m_file = nullptr;
        }
        ~MovableRaiiFileStream() noexcept
        {
            try {
                if(m_file)
                    m_file->close();
            } catch(...) {
                try {
                    std::cerr << "Unable to close file.";
                } catch(...) {
                }
            }
        }
        MovableRaiiFileStream& operator=(MovableRaiiFileStream&& other) noexcept
        {
            if(this != &other) {
                this->MovableRaiiFileStream::~MovableRaiiFileStream();
                new(this) MovableRaiiFileStream(std::move(other));
            }
            return *this;
        }
    };
    Ensuite, admettons que cette personne se dise que ce serait mieux que MovableRaiiFileStream<FileStreamType> soit responsable de gérer la mémoire du FileStreamType et que ce serait bien aussi de garder en mémoire le chemin du fichier. D'ailleurs, ce chemin pourra être affiché en cas d'échec de fermeture du fichier. Le code devient alors :
    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
    template<class FileStreamType>
    class MovableRaiiFileStream
    {
    private:
        std::unique_ptr<FileStreamType> m_file;
        std::string                     m_path;
     
        bool invariant()
        {
            return bool(m_file);
        }
    public:
        explicit MovableRaiiFileStream(const std::string& path) :
            m_file(new FileStreamType(path)), m_path(path)
        {
            if(!m_file->is_open())
                throw std::runtime_error("Unable to open file \"" + path + "\".");
        }
        MovableRaiiFileStream(MovableRaiiFileStream&& other) noexcept = default;
        ~MovableRaiiFileStream() noexcept
        {
            try {
                if(m_file)
                    m_file->close();
            } catch(...) {
                try {
                    std::cerr << "Unable to close file \"" << m_path << "\".";
                } catch(...) {
                }
            }
        }
        MovableRaiiFileStream& operator=(MovableRaiiFileStream&& other) noexcept
        {
            if(this != &other) {
                this->MovableRaiiFileStream::~MovableRaiiFileStream();
                new(this) MovableRaiiFileStream(std::move(other));
            }
            return *this;
        }
    };
    De la première à la deuxième version du code, le code du constructeur de mouvement a été supprimé (car le compilateur pouvait le générer) et celui du destructeur a changé. Mais celui de l'affectation de mouvement est inchangé, grâce à la factorisation.

    Pour l'instant, le principal inconvénient qui me vient à l'esprit à propos de cette pratique, c'est que le code ne sera compréhensible que par ceux qui connaissent le placement new.

    Qu'en pensez-vous ?

  2. #2
    Membre chevronné Avatar de Astraya
    Homme Profil pro
    Consommateur de café
    Inscrit en
    Mai 2007
    Messages
    1 043
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France

    Informations professionnelles :
    Activité : Consommateur de café
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mai 2007
    Messages : 1 043
    Points : 2 234
    Points
    2 234
    Par défaut
    Ou tout simplement:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    MovableRaiiFileStream& operator=(MovableRaiiFileStream&& other) noexcept
    {
        m_file = other.m_file;
        m_path = std::move(other.m_path);
        return *this;
    }
    Homer J. Simpson


  3. #3
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 470
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 470
    Points : 6 107
    Points
    6 107
    Par défaut
    Citation Envoyé par Astraya Voir le message
    Ou tout simplement:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    MovableRaiiFileStream& operator=(MovableRaiiFileStream&& other) noexcept
    {
        m_file = other.m_file;
        m_path = std::move(other.m_path);
        return *this;
    }
    Il y a trois problèmes avec ton code :
    1. m_file = other.m_file ne compile pas, car std::unique_ptr n'a pas d'affectation de copie. Tu voulais probablement écrire m_file = std::move(other.m_file).
    2. Tu ne fais que réimplémenter l'affectation de mouvement par défaut. Donc autant écrire directement :
      Code : Sélectionner tout - Visualiser dans une fenêtre à part
      MovableRaiiFileStream& operator=(MovableRaiiFileStream&& other) noexcept = default;
    3. [EDIT 22h14]Enfin, le plus gros problème, c'est que ton affectation de mouvement (alias affectation de mouvement par défaut) n'essaie pas de fermer le fichier de this avant de prendre en charge celui de other. L'objectif de la classe n'est donc pas rempli. Par contre, mon affectation de mouvement appelle explicitement le destructeur qui essaie bien de fermer le fichier de this.
      Erreur de ma part. J'avais cru que les destructeur de std::basic_ifstream et de std::basic_ofstream ne fermaient pas le fichier.
      Cependant, implémenter soi-même un destructeur qui ferme le fichier permet de choisir quoi faire en cas d'échec de fermeture. J'ai écrit dans std::cerr, mais j'aurais pu faire quelque chose de plus intéressant, comme écrire dans un fichier de log en précisant le niveau de log.[/EDIT]

  4. #4
    Membre chevronné Avatar de Astraya
    Homme Profil pro
    Consommateur de café
    Inscrit en
    Mai 2007
    Messages
    1 043
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France

    Informations professionnelles :
    Activité : Consommateur de café
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mai 2007
    Messages : 1 043
    Points : 2 234
    Points
    2 234
    Par défaut
    Un coup c'est un pointer, un coup c'est un unique_ptr... Je vois pas quel debat tu veux avoir. Ta solution est... "Degeulasse" pardonne moi l'expression.
    Pour le unique_ptr tu appelle reset(). Sinon tu delete le pointer avant de l'affecter. C'est tout. Le reste est sans intérêt.
    Homer J. Simpson


  5. #5
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 470
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 470
    Points : 6 107
    Points
    6 107
    Par défaut
    L'intérêt de factoriser le code de l'affectation de mouvement en appelant explicitement le destructeur puis le constructeur de mouvement, c'est de ne pas avoir besoin de changer le code de l'affectation de mouvement lorsque le code du destructeur et/ou celui du constructeur de mouvement change.

    Citation Envoyé par Astraya Voir le message
    Je vois pas quel debat tu veux avoir.
    J'avais fait une recherche sur internet à propos de ma solution.
    Quelqu'un avait évoqué cette solution ici : http://stackoverflow.com/questions/2...ve-constructor
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    Class& operator=(Class&& rhs)
    {
        this->~Class();                    // call destructor
        new (this) Class(std::move(rhs));  // call move constructor in-place
    }
    Les deux contre-arguments qu'il avait reçu étaient :
    1. Le code n'était pas adapté dans le cas où le destructeur était virtuel (corrigeable en remplaçant this->~Class() par this->Class::~Class()).
    2. Le code risquait d'être difficile à maintenir à cause de la syntaxe assez rare. Je l'ai évoqué dans mon premier message : « le principal inconvénient qui me vient à l'esprit à propos de cette pratique, c'est que le code ne sera compréhensible que par ceux qui connaissent le placement new ».


    A part ça, j'avais lu un vieux Guru of the week du 11 octobre 1997 qui critiquait l'implémentation de l'affectation de copie en fonction en fonction du destructeur et du constructeur de copie :
    http://www.gotw.ca/gotw/023.htm
    Mais cela ne me semblait pas transposable à l'affectation de mouvement.

    J'ai publié ce fil ici au cas où la technique évoquée dans mon premier message aurait des inconvénients qui m'auraient échappés.

  6. #6
    Expert éminent sénior

    Avatar de dragonjoker59
    Homme Profil pro
    Software Developer
    Inscrit en
    Juin 2005
    Messages
    2 031
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Bas Rhin (Alsace)

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

    Informations forums :
    Inscription : Juin 2005
    Messages : 2 031
    Points : 11 379
    Points
    11 379
    Billets dans le blog
    10
    Par défaut
    Ben avec le unique_ptr, aucun intérêt à utiliser ta méthode, car si le FileStreamType est bien fait (RAII tout ça), il fermera le fichier à sa destruction et c'est réglé...
    Si vous ne trouvez plus rien, cherchez autre chose...

    Vous trouverez ici des tutoriels OpenGL moderne.
    Mon moteur 3D: Castor 3D, presque utilisable (venez participer, il y a de la place)!
    Un projet qui ne sert à rien, mais qu'il est joli (des fois) : ProceduralGenerator (Génération procédurale d'images, et post-processing).

  7. #7
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 470
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 470
    Points : 6 107
    Points
    6 107
    Par défaut
    Citation Envoyé par dragonjoker59 Voir le message
    Ben avec le unique_ptr, aucun intérêt à utiliser ta méthode, car si le FileStreamType est bien fait (RAII tout ça), il fermera le fichier à sa destruction et c'est réglé...
    T'as publié quelques minutes après mon édition de mon 2e message. On s'est croisé.
    Citation Envoyé par Pyramidev Voir le message
    Erreur de ma part. J'avais cru que les destructeur de std::basic_ifstream et de std::basic_ofstream ne fermaient pas le fichier.
    Cependant, implémenter soi-même un destructeur qui ferme le fichier permet de choisir quoi faire en cas d'échec de fermeture. J'ai écrit dans std::cerr, mais j'aurais pu faire quelque chose de plus intéressant, comme écrire dans un fichier de log en précisant le niveau de log.

Discussions similaires

  1. Plus d'appel au destructeur qu'au constructeur ?
    Par webshaker dans le forum Débuter
    Réponses: 26
    Dernier message: 23/03/2015, 09h28
  2. appel service web puis fermeture appli appelante
    Par gerald2545 dans le forum Windows Forms
    Réponses: 5
    Dernier message: 22/08/2007, 12h27
  3. Réponses: 3
    Dernier message: 31/07/2007, 21h18
  4. [Loufoque] Appel de destructeur
    Par Chii-san dans le forum C++
    Réponses: 12
    Dernier message: 18/05/2006, 10h16

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