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 :

Différence entre std::unique_ptr et std::shared_ptr.(Quand les utiliser ?)


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Invité
    Invité(e)
    Par défaut Différence entre std::unique_ptr et std::shared_ptr.(Quand les utiliser ?)
    Salut.

    J'hésite souvent entre les 2.

    std::unique_ptr me donne souvent des erreurs du style use of deleted function. (Je ne l'utilise presque jamais à vrai dire)

    std::shared_ptr : j'ai moins de problème avec.

    Si j'ai bien compris la documentation std::unique_ptr est préférable si le pointeur n'est pas copiable, c'est bien ça ?

    Donc il faut obligatoirement redéfinir un move constructor pour transférer le contenu d'un pointeur à l'autre.

    Donc à priori std::unique_ptr ne serait à utiliser que pour les pointeurs non copiable.

    Tandis que std::shared_ptr si j'ai bien compris, est à utiliser pour les pointeurs qui peuvent être copiés.

    Donc si je comprends bien, std::unique_ptr est préférable dans le cas ou un objet ne peux être référencer que par un seul pointeur à la fois dans tout le programme.

    Je ne comprend pas très bien leur intérêt. :/

  2. #2
    En attente de confirmation mail

    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 : 33
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Bonjour,

    L'utilité des pointeurs intelligents (peu importe lesquels) c'est de gérer le cycle de vie d'une ressource. Leur nécessité est du à la grande difficulté (impossibilité) de gérer manuellement (avec new/delete ou tout autre fonctions de création/libération à appeler systématiquement) les ressources en C++ dès qu'on dépasse le cadre de une ressource (et encore).

    Gérer le cycle de vie va donc consisté à appeler automatiquement la fonction de libération (delete) et à associer le plus tôt possible le pointeur intelligent à la fonction de création (new). Techniquement on se retrouve donc avec une capsule RAII (unique_ptr/shared_ptr) et sa fonction de création (make_unique/make_shared).

    Maintenant la différence tiens à savoir qui gère la ressource, ou plus exactement combien de personne en même temps. Si la ressource doit n'être gérer que par une personne, alors c'est unique_ptr qui convient, si par contre plusieurs personne doivent la gérer alors c'est shared_ptr (et son copain weak_ptr). On doit plus souvent se retrouver dans le cas de unique_ptr.

    Pour l'utilisation, non il n'y a rien de spécial à redéfinir, tes erreurs sont surement du à la tentative de copier un unique_ptr, ce qui est une erreur, il y a un seul propriétaire, quel sens donner à la copie ? On peut par contre le transférer (avec move).

  3. #3
    Invité
    Invité(e)
    Par défaut
    Ok donc si j'ai bien compris :

    std::unique_ptr est plutôt à utilisé si, par exemple, un personnage possède un pointeur sur une arme et lors de la création d'un autre personnage prenant la même arme que le 1er personnage, le pointeur sur l'arme doit être ré-alloué et tout le contenu de la classe arme doit être copier.

    std::shared_ptr est plutôt à utilisé dans l'autre cas, si par exemple, j'ai une ressource qui possède un pointeur sur une texture et je veux que l'autre ressource contienne aussi un pointeur pointant sur la même texture.

    Donc, j'utiliserais plutôt std::unique_ptr dans le cadre de classes copiable (ce qui est en effet la sémantique la plus souvent utilisée) et std::shared_ptr dans le cadre de classes non copiable.

    Bref je vais essayer de remplacer tout les raws pointers utilisé dans mon framework par des std::unique_ptr et std::shared_ptr.

  4. #4
    En attente de confirmation mail

    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 : 33
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Pour le premier exemple, non rien n'est ré-alloué, je vais prendre cet exemple. On a donc une classe character et une classe weapon :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    struct weapon
    {
    };
     
    struct character
    {
    };
    Premier point, quel sémantique de weapon ? A priori entité, donc non copiable (par contre déplaçable) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    struct weapon
    {
      weapon() =default;
      weapon(weapon&) =delete;
      weapon(weapon&&) =default;
      weapon& operator=(weapon&) =delete;
      weapon& operator=(weapon&&) =default;
    };
     
    struct character
    {
    };
    (Je lui ai aussi mis un constructeur par défaut pour que ca compile). Maintenant la sémantique de character, c'est la même. On a aussi un lien entre weapon et character l'un peut posséder l'autre et c'est l'unique possesseur : c'est typiquement le cas d'utilisation d'un std::unique_ptr (unicité et facultatif ou changeable) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    struct weapon
    {
      weapon() =default;
      weapon(weapon&) =delete;
      weapon(weapon&&) =default;
      weapon& operator=(weapon&) =delete;
      weapon& operator=(weapon&&) =default;
    };
     
    class character
    {
      std::unique_ptr<weapon> hand;
    };
    Je profite de la sémantique de std::unique_ptr qui est entité et qui s'impose à character par transitivité pour ne pas réécrire les services delete/default. Ensuite on va dire qu'un personnage peut laisser tomber son arme et qu'il peut prendre une arme (et dans ce cas il lâche celle qu'il possède) :
    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
     
    struct weapon
    {
      weapon() =default;
      weapon(weapon&) =delete;
      weapon(weapon&&) =default;
      weapon& operator=(weapon&) =delete;
      weapon& operator=(weapon&&) =default;
    };
     
    struct character
    {
      auto release()
      {
        using std::swap;
     
        std::unique_ptr<weapon> empty; //On créé une main vide
        swap(hand,empty);                    //On échange la main vide avec notre main
        return empty;                            //On retourne la main qu'on avait créé
      }
      auto equip(std::unique_ptr<weapon>&& to_equip)
      {
        using std::swap;
     
        swap(hand,to_equip);           //On échange notre main avec l'arme qu'on veut
        return std::move(to_equip);  //On retourne ce qu'on avait dans la main
      }
    private:
      std::unique_ptr<weapon> hand;
    };
    (On peut faire un peu autrement, mais je trouve que c'est ainsi que c'est sémantiquement le plus clair). Un main pour illustrer tout ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    int main()
    {
    	character david;                                      //David apparait
    	david.equip(std::make_unique<weapon>());  //Il s'équipe d'une arme
    	character goliath;                                    //Goliath arrive
    	goliath.equip(david.release());                    //David laisse tomber son arme et Goliath s'en équipe
    }
    Pour les shared_ptr j'ai pas d'exemple d'utilisation, je ne sais pas si c'est ce qui convient pour des textures.

  5. #5
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 374
    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 374
    Points : 41 541
    Points
    41 541
    Par défaut
    Normalement, les deux tendent à être utilisés avec des classes non-copiables: Tout dépend de qui gère la durée de vie.

    Par exemple, un conteneur (vector, map etc.) de unique_ptr est une bonne idée, préférable à shared_ptr, si tu veux que le conteneur gère la durée de vie du contenu.
    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
    Invité
    Invité(e)
    Par défaut
    Ha ok donc ça ne convient pas vraiment dans le cadre des classes copiable alors ?

    Alors que devrais je faire dans le cadre des classes copiable, faire une méthode clone et, recréer un std::unique_ptr ?

    Bref dans les exemple que flob90 m'a donné, il faisait ça.

  7. #7
    En attente de confirmation mail

    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 : 33
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Pas nécessairement, disons que A et B sont copiables et que A gère un std::unique_ptr sur B :
    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
     
    struct A {};
     
    struct B
    {
      B() =default;
      B(const B& rhs)
        : a(rhs.a ? std::make_unique<A>(*rhs.a) : std::unique_ptr<A>())
      {}
      B& operator=(const B& rhs)
      { a=std::make_unique<A>(*rhs.a); return *this; }
     
    private:
      std::unique_ptr<A> a;
    };
    Dans le code que j'ai écrit sur l'autre sujet, j'utilise un fonction clone car on est dans une hiérarchie d'héritage, ça me permet d'avoir un notion de copie/clonage sans avoir le slicing.

  8. #8
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 625
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 625
    Points : 30 674
    Points
    30 674
    Par défaut
    Salut,
    Citation Envoyé par Lolilolight Voir le message
    Ha ok donc ça ne convient pas vraiment dans le cadre des classes copiable alors ?

    Alors que devrais je faire dans le cadre des classes copiable, faire une méthode clone et, recréer un std::unique_ptr ?

    Bref dans les exemple que flob90 m'a donné, il faisait ça.
    Typiquement, une classe a soit sémantique de valeur, soit sémantique d'entité (du moins, si on exclu les conteneurs de la réflexion, car il prennent la sémantique des éléments qu'ils contiennent ) et une classe ayant sémantique de valeur est copiable et affectable (mais il est généralement bon de veiller à ce que ce soit une classe non modifiable) alors qu'une classe ayant sémantique d'entité n'est ni copiable ni affectable (mais il est généralement possible d'en modifier l'état).

    Les classes ayant sémantique de valeur correspondent systématiquement à des objets dont on peu retrouver différentes instances strictement identiques en mémoire à un instant donné et peuvent assez facilement entrer dans la catégorie des "types primitifs de ton DSL (Domain specific language, ou, si tu préfères, les termes que tu utilises pour expliquer les données métier que tu dois manipuler) et qui n'ont aucun sens à entrer dans une hiérarchie de classe (à intervenir dans un relation d'héritage). Une date, une heure, une couleur, une coordonnée, un montant ou une unité (metre, seconde, euro, gramme, ...) sont autant de classes ayant sémantique de valeur.

    Les classes ayant sémantique d'entité correspondent quant à elles à des objets qui doivent être identifiable de manière unique et non ambigue. Tu ne peux donc pas trouver, à un instant T, plusieurs instances représentant exactement les même valeurs, autrement, tu perds la notion d'unicité, et tu risque d'appliquer à l'une des changements qui auraient du être appliqués à d'autres. Les classes ayant sémantique d'entité sont les candidates idéales pour créer des hiérarchies de classes (qu'elles soient des classes de base ou des classes dérivées). Vehicule, Arme, CompteBanquaire etc sont autant de classes ayant sémantique d'entité.

    Si je place la classe Arme dans cette catégorie, c'est parce que tu peux en effet avoir plusieurs classes qui héritent de cette classe (Epée, dague, arc, sarbaccane, lance-pierre, j'en passe et de meilleures, entrent toutes dans la catégorie "armes" )

    A priori, le problème ne devrait donc pas se poser pour les classes copiables, car il s'agit de classe ayant sémantique de valeur qui n'interviendront donc jamais dans une hiérarchie de classes et pour lesquelles il n'y a donc absolument aucune raison de recourir à l'allocation dynamique de la mémoire. Et, si tu n'as aucune raison de recourir à l'allocation dynamique de la mémoire, tu n'as donc aucune raison d'envisager le recours à une capsule RAII afin d'assurer la libération correcte de la mémoire au moment où cela s'avère opportun

    L'idée générale est donc :
    1- Utiliser l'allocation dynamique de la mémoire uniquement lorsque tu en as besoin, c'est à dire lorsque tu veux pouvoir disposer de collections contenant des objets dont le type entre dans la catégorie des classes ayant sémantique de valeur (comme ces objets ne sont ni copiable ni affectables, l'utilisation de pointeurs devient la seule solution pour créer ce type de collection et l'allocation dynamique de la mémoire s'avère être la seule solution permettant d'avoir la certitude que les objets renvoyés ne seront pas détruits par mégarde).

    2-Si tu dois recourir à l'allocation dynamique de la mémoire, tu devrais l'encapsuler dans une ressource RAII (std::unique_ptr ou std::shared_ptr)

    3- Tu devrais, autant que possible, faire en sorte de n'avoir qu'un et un seul objet qui sera responsable de la libération des ressources allouées dynamiquement, mais ce n'est pas forcément toujours possible

    4- Si tu arrive à faire en sorte qu'il n'y a qu'un et un seul décideur du moment où les ressources allouées à un objet doivent être libérées (comprends : si tu n'as qu'un et un seul responsable de la durée de vie pour les objets pour lesquels tu as eu recours à l'allocation dynamique de la mémoire, même s'il est possible de "passer la patate chaude" à quelqu'un d'autre), std::unique_ptr est ce qu'il te faut

    5- Si tu n'arrive pas à faire en sorte qu'il n'y ait qu'un et un seul décideur pour ce qui est de la durée de vie des objets pour lesquels tu as eu recours à l'allocation dynamique de la mémoire, tu devras "en désespoir de cause" te tourner vers std::shared_ptr (et son pendant std::weak_ptr)
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

Discussions similaires

  1. Réponses: 19
    Dernier message: 29/09/2014, 17h12
  2. Réponses: 2
    Dernier message: 04/08/2014, 13h43
  3. Exceptions, quand les utiliser
    Par JuTs dans le forum Général Dotnet
    Réponses: 6
    Dernier message: 13/05/2007, 15h27
  4. [Smarty] Utilité ? Quand les utiliser ?
    Par Xunil dans le forum Bibliothèques et frameworks
    Réponses: 25
    Dernier message: 28/11/2006, 17h08
  5. fonctions et classes... quand les utiliser ?
    Par fastmanu dans le forum Langage
    Réponses: 6
    Dernier message: 03/04/2006, 00h39

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