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 :

Classe ressource et semantique move


Sujet :

Langage C++

  1. #1
    Membre actif

    Profil pro
    Inscrit en
    avril 2010
    Messages
    356
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : avril 2010
    Messages : 356
    Points : 206
    Points
    206
    Par défaut Classe ressource et semantique move
    Bonjour,

    J'essaie de créer une classe générale de gestion de ressources et je souhaite comprendre la sémantique move.

    Voici la partie de code nécessaire à la compréhension de la classe :

    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
    template <typename T, template <typename ElementType, typename... others> class Cont>
    class Ressource;
     
    template <typename T>
    class Ressource<T, std::vector>
    {
        private :
            std::vector<T*> v;
        public :
            struct Identifier
            {
                friend class Ressource;
                private :
                    const Ressource& r;
                    unsigned pos;
                public :
                    Identifier(const Ressource& pr, unsigned p) : r(pr), pos(p) {}
                    T& getObject()
                    {
                        if(r.v[pos]!=NULL)
                            return *r.v[pos];
                        else
                            throw std::runtime_error("Accessing deleted Object");
                    }
                    const T& getObject() const
                    {
                        if(r.v[pos]!=NULL)
                            return *r.v[pos];
                        else
                            throw std::runtime_error("Accessing deleted Object");
                    }
            };
            Ressource() = default;
            Ressource(const Ressource&) = delete;
            Ressource& operator=(const Ressource&) = delete;
            ~Ressource()
            {
                for(auto it = v.begin(); it!=v.end();it++)
                    if(*it!=NULL)
                        delete *it;
            }
            Identifier addObject(const T& obj)
            {
                v.push_back(new T(obj));
                return Identifier(*this, v.size()-1);
            }
            void deleteObject(const Identifier& i)
            {
                delete v[i.pos];
                v[i.pos] = NULL;
            }
    };
    Ma question est :
    Que dois-je rajouter pour que lorsque l'utilisateur fait ressource.addObject(T(parametres...)), je rajoute l'adresse de l'objet temporaire créé et je dis au compilateur de ne pas le supprimer ? Note : un delete sera par la suite effectué sur cet objet.

    Je pense bouger le constructeur de Identifier en privé et mettre un constructeur de copie en public et créer une classe d'exception spécifique.
    Je vais aussi rajouter des itérators pour pouvoir itérer sur la collection sans garantir l'ordre de parcours.
    Peut-être que je devrais spécialiser en fonction de l'existence d'un randomaccessiterator plutôt que de directement spécialiser sur vector (dans le futur list, ...) ?
    Peut-être que je devrais créer une classe Const_Identifer avec Identifer convertible en Const_Identifer parce que aujourd'hui const Identifer joue ce role mais cela ne semble pas parfait... Est-ce possible sans pertes de performance lors de la convertion ?
    Si jamais vous avez d'autres remarques, n'hésitez pas !

    Merci d'avance !

  2. #2
    Membre éprouvé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    juin 2014
    Messages
    345
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Finance

    Informations forums :
    Inscription : juin 2014
    Messages : 345
    Points : 1 211
    Points
    1 211
    Par défaut
    Hum—qu'on me corrige si nécessaire—je ne pense pas qu'implémentée telle quelle, ta classe ressource puisse accueillir des xvalues.
    Premièrement il n'est absolument pas possible de récupérer l'adresse d'une rvalue. Ce qui veut dire que tu ne peux pas récupérer l'adresse d'un objet temporaire. C'est logique, on ne peut pas récupérer l'adresse d'une valeur, genre &(3) ne veut rien dire. 3 pourrait être aussi bien stocké en mémoire, que dans un registre, qu'écrit directement dans les instructions machine.
    Ensuite tu utilises un std::vector<T*> donc un vecteur d'adresses ... or comme on ne peut pas récupérer l'adresse d'une rvalue, pour insérer cette valeur, il faut nécessairement la copier dans un objet adressable. Voilà qui annule l'intérêt de la "sémantique move".
    Bref, si tu me suis, implémentée telle quelle, ta classe pourra difficilement exploiter les move semantics.

    Au passage, dans la fonction addObject() que tu postes, tu prends un const T& pour ensuite faire une copie de cette valeur (new T(...)), alors que tu pourrais simplement récupérer l'adresse du paramètre et l'ajouter à la liste. Mais d'après ce que j'ai compris, ta classe est faite pour conserver des copies des objets que tu ajoutes, donc encore une fois, je ne vois pas trop l'intérêt du move ici.

    Une autre remarque : quand c'est possible, il vaut mieux éviter les template template ou toute construction un peu "trop" complexe ... au lieu de prendre une classe template en paramètre template, j'aurais plutôt pris une classe simple sur laquelle j'aurais fait un rebind si nécessaire. Ton code deviendrait :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template <typename T, class Cont = std::vector<T*> >
      class Resource { /*...*/ };
     
    template <typename T>
      class Resource<T, std::vector<T*> > {/*...*/};
    En plus de relativement simplifier le code, cela permet plus de flexibilité/généricité ... en effet si tu essaies d'utiliser un conteneur autre que ceux de la librairie standard ou, de manière générale, toute classe template n'ayant pas des paramètres templates de la forme <T, Args...>, cela ne fonctionnera pas. Je t'invite à te renseigner sur la structure template rebind des conteneurs de la STL, si ça t'intéresse ; mais évidemment ce n'est pas le sujet.

  3. #3
    Membre actif

    Profil pro
    Inscrit en
    avril 2010
    Messages
    356
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : avril 2010
    Messages : 356
    Points : 206
    Points
    206
    Par défaut
    L'idée derrière l'utilisation d'un vecteur de T* et non un vecteur de T est d'économiser de la place en cas de suppression d'un élément (car on ne le supprime pas vraiment).

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template <typename T, class Cont = std::vector<T*> >
      class Resource { /*...*/ };
     
    template <typename T>
      class Resource<T, std::vector<T*> > {/*...*/};
    Comment l'utilisateur sait-il que j'utilise un conteneur de T* en interne et non un conteneur de T ? Pour la spécialisation sur les listes, je prévois d'utiliser un conteneur de T par exemple. Cependant, l'interface devrait plutôt être template<typename T, template<typename> class Cont>. Mais pour des raisons de simplicité d'utilisation j'ai permis d'avoir plus de paramètres.

    Au passage, dans la fonction addObject() que tu postes, tu prends un const T& pour ensuite faire une copie de cette valeur (new T(...)), alors que tu pourrais simplement récupérer l'adresse du paramètre et l'ajouter à la liste. Mais d'après ce que j'ai compris, ta classe est faite pour conserver des copies des objets que tu ajoutes, donc encore une fois, je ne vois pas trop l'intérêt du move ici.
    C'est une question de responsabilité : la variable passée à addObject peut très bien être détruite avant la classe Ressource.
    Hum—qu'on me corrige si nécessaire—je ne pense pas qu'implémentée telle quelle, ta classe ressource puisse accueillir des xvalues.
    Premièrement il n'est absolument pas possible de récupérer l'adresse d'une rvalue. Ce qui veut dire que tu ne peux pas récupérer l'adresse d'un objet temporaire. C'est logique, on ne peut pas récupérer l'adresse d'une valeur, genre &(3) ne veut rien dire. 3 pourrait être aussi bien stocké en mémoire, que dans un registre, qu'écrit directement dans les instructions machine.
    Ensuite tu utilises un std::vector<T*> donc un vecteur d'adresses ... or comme on ne peut pas récupérer l'adresse d'une rvalue, pour insérer cette valeur, il faut nécessairement la copier dans un objet adressable. Voilà qui annule l'intérêt de la "sémantique move".
    Bref, si tu me suis, implémentée telle quelle, ta classe pourra difficilement exploiter les move semantics.
    Y a t-il un moyen pour que quand l'utilisateur face r.addObject(T(...)), on ne créé pas un T temporaire puis une copie sur le tas puis on détruit le T temporaire mais plutôt qu'on créé un T sur le tas directement ?

  4. #4
    Membre expérimenté Avatar de Trademark
    Profil pro
    Inscrit en
    février 2009
    Messages
    762
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : février 2009
    Messages : 762
    Points : 1 396
    Points
    1 396
    Par défaut
    L'idée derrière l'utilisation d'un vecteur de T* et non un vecteur de T est d'économiser de la place en cas de suppression d'un élément (car on ne le supprime pas vraiment).
    Ça veut dire que ton vecteur ne fera que croître et si tu l'utilises assez longtemps (dans un serveur par exemple) tu manqueras de mémoire à un moment ou un autre... C'est très bof surtout que c'est caché par ton interface. Mais bon je ne connais pas la problématique générale alors je me goure peut-être.

    Pour ton problème, tu peux soit faire :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    template <Args... args>
    Identifier addObject(Args&&... obj)
            {
                v.push_back(new T(std::forward<Args>(args)...));
                return Identifier(*this, v.size()-1);
            }
    C'est une opération "emplace", voir la doc de std::vector. On "forward" les arguments du constructeur de T, à T comme ça il n'y a pas de copie temporaire.

    Ou encore :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    Identifier addObject(T&& obj)
            {
                v.push_back(new T(std::move(obj));
                return Identifier(*this, v.size()-1);
            }
    Overloadé avec la classique T const& (comme pour push_back de std::vector).

    En tout cas, je pense que ce que tu essayes de faire n'est pas génial génial mais difficile de juger sans avoir plus de contexte.

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

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

    Informations forums :
    Inscription : septembre 2005
    Messages : 27 272
    Points : 41 183
    Points
    41 183
    Par défaut
    Pour un vecteur de pointeurs, addObject() devrait prendre en paramètre un unique_ptr<T> plutôt qu'un const T &.
    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. #6
    Membre actif

    Profil pro
    Inscrit en
    avril 2010
    Messages
    356
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : avril 2010
    Messages : 356
    Points : 206
    Points
    206
    Par défaut
    Ça veut dire que ton vecteur ne fera que croître et si tu l'utilises assez longtemps (dans un serveur par exemple) tu manqueras de mémoire à un moment ou un autre... C'est très bof surtout que c'est caché par ton interface. Mais bon je ne connais pas la problématique générale alors je me goure peut-être.
    Je sais, c'est pourquoi je prévois de rajouter des politiques comme "quand le vecteur est trop vide, je le recondense". L'idée est que la spécialisation pour vecteur doit être utilisée si les objets ne vont pas être trop souvent supprimés. Si les objets sont souvent supprimés, il vaudra mieux utiliser la spécialisation pour les listes.

    Merci pour les deux méthodes d'utiliser move.

    Pour un vecteur de pointeurs, addObject() devrait prendre en paramètre un unique_ptr<T> plutôt qu'un const T &
    Pourquoi ?

  7. #7
    Membre éprouvé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    juin 2014
    Messages
    345
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Finance

    Informations forums :
    Inscription : juin 2014
    Messages : 345
    Points : 1 211
    Points
    1 211
    Par défaut
    Citation Envoyé par Médinoc Voir le message
    Pour un vecteur de pointeurs, addObject() devrait prendre en paramètre un unique_ptr<T> plutôt qu'un const T &.
    C'est vrai que sémantiquement, ça va plutôt bien avec ce qui est requis de la classe Ressource, qui récupère la propriété de l'objet (ownership). Après c'est bien moins pratique pour l'utilisateur de la classe, et pas forcément logique au niveau de l'interface. addObject() attend un objet, pas une référence (edit: sémantiquement parlant) ...

  8. #8
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : novembre 2008
    Messages : 1 505
    Points : 2 798
    Points
    2 798
    Par défaut
    Citation Envoyé par the Hound Voir le message
    C'est vrai que sémantiquement, ça va plutôt bien avec ce qui est requis de la classe Ressource, qui récupère la propriété de l'objet (ownership). Après c'est bien moins pratique pour l'utilisateur de la classe, et pas forcément logique au niveau de l'interface. addObject() attend un objet, pas une référence (edit: sémantiquement parlant) ...
    Sémantiquement, une ressource, c’est non-copiable. Les ressources copiables, c’est une catastrophe (impossible de savoir précisément qui doit réellement la détruire et quand, sauf à faire du refcount avec tous les problèmes potentiels).

    Dans le standard, il y a une classe pour gérer les ressources : std::unique_ptr. On peut spécialiser le destructeur pour appeler correctement la bonne fonction de libération à la destruction. Après, si on n’a pas accès à C++11, je ne suis pas sûr que ça vaille le coup de s’embêter à faire une classe « générique » pour : il manque dans le langage des mécanismes pour le faire vraiment correctement.

  9. #9
    Membre émérite
    Profil pro
    Inscrit en
    novembre 2004
    Messages
    2 750
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : novembre 2004
    Messages : 2 750
    Points : 2 660
    Points
    2 660
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Après, si on n’a pas accès à C++11, je ne suis pas sûr que ça vaille le coup de s’embêter à faire une classe « générique » pour : il manque dans le langage des mécanismes pour le faire vraiment correctement.
    auto_ptr ?

  10. #10
    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 : 47
    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 oodini Voir le message
    auto_ptr ?
    À éviter comme la peste... un pointeur qui déplace quand on lui demande de copier, c'est vraiment un piège ! J'ai eu des collègues qui l'avaient utilisé, presqu'à chaque fois il y avait un problème trouvé bien plus tard...

    Éventuellement un auto_ptr const, au moins, avec lui, on ne risque pas de déplacer sans s'en rendre compte...

    Mais sinon, passer à un compilo plus récent n'est pas forcément une mauvaise idée
    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.

  11. #11
    Membre émérite
    Profil pro
    Inscrit en
    novembre 2004
    Messages
    2 750
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : novembre 2004
    Messages : 2 750
    Points : 2 660
    Points
    2 660
    Par défaut
    Citation Envoyé par JolyLoic Voir le message
    À éviter comme la peste... un pointeur qui déplace quand on lui demande de copier, c'est vraiment un piège !
    Ben justement : NoIdea ne demande pas une copie, mais un déplacement.

    Citation Envoyé par JolyLoic Voir le message
    Mais sinon, passer à un compilo plus récent n'est pas forcément une mauvaise idée
    Ça ne dépend peut-être pas de lui, comme souvent...

  12. #12
    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 : 47
    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 oodini Voir le message
    Ben justement : NoIdea ne demande pas une copie, mais un déplacement.
    Certes, mais un déplacement qui a lieu avec une syntaxe de copie, ça pose des soucis. Un jour, quelqu'un refactorera le code, passer le pointeur par valeur à une fonction, ignorant qu'ne faisant ça, il a détruit la donnée initiale. Pour faire la même chose avec unique_ptr, il faut explicitement appeler f(std::move(p)), c'est bien plus clair.
    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.

  13. #13
    Membre émérite
    Profil pro
    Inscrit en
    novembre 2004
    Messages
    2 750
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : novembre 2004
    Messages : 2 750
    Points : 2 660
    Points
    2 660
    Par défaut
    Je suis bien d'accord, mais faute de grives, on mange des merles.
    Et après tout, un codeur digne de ce nom devrait avoir lu Meyers ou autre bouquin du même acabit, et donc savoir ce qui se cache derrière un auto_ptr.

  14. #14
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    juin 2007
    Messages
    5 154
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : juin 2007
    Messages : 5 154
    Points : 16 976
    Points
    16 976
    Par défaut
    ou simplement la documentation, ou nos superbes cours...
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

Discussions similaires

  1. classe Ressource (R) "cannot be resolved"
    Par barbidur dans le forum Android
    Réponses: 5
    Dernier message: 04/12/2010, 19h43
  2. classe Ressource (R) "cannot be resolved"
    Par cad13 dans le forum Android
    Réponses: 16
    Dernier message: 18/02/2010, 20h43
  3. Réponses: 4
    Dernier message: 12/03/2008, 13h18
  4. Accès au fichier ressources depuis une classe action
    Par root76 dans le forum Struts 1
    Réponses: 2
    Dernier message: 21/11/2006, 07h36
  5. [C#] La ressource 'skin.bmp' est introuvable dans la classe?
    Par cortex024 dans le forum Windows Forms
    Réponses: 6
    Dernier message: 21/01/2006, 00h24

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