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++

  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 369
    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 369
    Points : 41 518
    Points
    41 518
    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
    Invité
    Invité(e)
    Par défaut
    Ok, je comprends mieux maintenant, merci.

  9. #9
    Invité
    Invité(e)
    Par défaut Une dernière question.
    Salut, si je suis dans ce cas là :

    J'ai un std::vector qui possède des pointeurs sur toutes les entités du monde. (ce vecteur là sert aussi de propriétaire qui libère les ressources)
    J'ai un second sdt::vector qui contient des pointeurs sur les entités du monde qui sont visible par le joueur.

    Le second std::vector n'est donc pas propriétaire, il utilise juste la ressource.

    Il n'y a pas de std::weak_ptr pour les std::unique_ptr.

    Comment dois je procéder pour convertir les std::unique_ptr en std::shared_ptr voir même en std::weak_ptr ?

  10. #10
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    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 369
    Points : 41 518
    Points
    41 518
    Par défaut
    Références (possiblement avec reference_wrapper?), pointeurs nus ou indices.
    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.

  11. #11
    Invité
    Invité(e)
    Par défaut
    Ok.

    Pour l'instant j'utilise des pointeurs nus.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 611
    Points
    30 611
    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

  13. #13
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    393
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 393
    Points : 684
    Points
    684
    Par défaut
    Citation Envoyé par koala01 Voir le message
    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)
    Je trouve que ces 2 derniers points sont incomplets.

    A mon avis :
    • un seul propriétaire et un seul observateur (le propriétaire) ==> unique_ptr
    • plusieurs propriétaire OU pluisuers observateur ==> shared_ptr


    Par défaut, on doit considérer que l'on est en multithreads (soit parce que c'est effectivement le cas, soit parce que rien n'interdit dans le design que ça ne sera pas le cas dans le futur). Dans cette situation, avec unique_ptr, pour avoir un observateur, on peut passer :

    • par un pointeur nu ==> impossible de vérifier si l'objet est détruit ou non
    • une référence ou un équivalent de weak_ptr ==> impossible de garantir que l'objet n'est pas détruit entre le moment où l'on teste la validité de l'objet et le moment où on l'utilise


    Dans les 2 cas, le design est problématique

    Le seul moyen sécurisé d'avoir plusieurs observateurs est d'utiliser shared_ptr/weak_ptr. Le premier est utilisé pour avoir plusieurs propriétaire de la ressource (situation qui devrait reste rare), le second pour avoir plusieurs observateurs sur la ressource. Lorsqu'un observateur souhaite utiliser la ressource, il doit localement promouvoir le weak_ptr en shared_ptr (et dans ce cas, le shared_ptr est détruit à la fin du bloc, l'observateur est juste un propriétaire temporaire)

    Donc à mon sens, le cas 4 correspond à unique_ptr si pas d'autre observateur que le propriétaire ou shared_ptr/weak_ptr s'il y a plusieurs observateurs

  14. #14
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    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 369
    Points : 41 518
    Points
    41 518
    Par défaut
    Ça dépend de la durée de vie des observateurs eux-mêmes (ou de leurs liens vers l'objet). Selon les circonstances, ils peuvent être garantis être détruits avant l'objet lui-même.
    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.

  15. #15
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    393
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 393
    Points : 684
    Points
    684
    Par défaut
    Citation Envoyé par mintho carmo Voir le message
    soit parce que rien n'interdit dans le design que ça ne sera pas le cas dans le futur
    Cela fait parti des contraintes sur le design dont je parlais.

    Effectivement, si le propriétaire et l'observateur sont dans le même thread, même si l'application est multi-threads, on pourra raisonner en termes de thread unique. Ou si le design fait que le thread ont une durée de vie plus courte que le propriétaire (par exemple si le propriétaire est un pool d'objets, qu'il ne les détruit jamais - sauf en quittant l'application)

    Mais cela a un impact en termes d'évolutivité du code. Un code conçu directement avec shared_ptr/weak_ptr sera plus robuste qu'un code conçu avec unique_ptr/& (ou weak_unique_ptr). Il faut voir si le coût d'utilisation de shared_ptr est critique ou non. Par défaut, je conseille son utilisation

  16. #16
    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
    Par défaut, je "déconseille" son utilisation et demande de bien identifier qui est responsable de la durée de vie de l'objet à l'instant t dans la vie du programme et pas dans les éventuelles améliorations / évolutions qui ne seront probablement jamais faites. (C'est plus facile de passer d'un unique_ptr à un shared_ptr que l'inverse.)

  17. #17
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    393
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 393
    Points : 684
    Points
    684
    Par défaut
    Citation Envoyé par Ehonn Voir le message
    Par défaut, je "déconseille" son utilisation et demande de bien identifier qui est responsable de la durée de vie de l'objet à l'instant t dans la vie du programme et pas dans les éventuelles améliorations / évolutions qui ne seront probablement jamais faites. (C'est plus facile de passer d'un unique_ptr à un shared_ptr que l'inverse.)
    Dans ce que j'explique, je considère bien le cas où il n'y a qu'un seul responsable de la durée de vie d'un objet.

    Je ne parle pas de ne pas définir clairement qui est responsable de la durée de vie ou d'utiliser des shared_ptr pour ne pas avoir à réfléchir sur cette problématique. On est bien d'accord que d'utiliser par défaut shared_ptr pour éviter d'avoir à réfléchir au design est une très mauvaise approche.

    Je parle d'avoir un propriétaire unique (1 seul shared_ptr permanent - donc un seul responsable de la durée de vie) et plusieurs observateurs (plusieurs weak_ptr, qui ne sont pas responsable de la durée de vie de l'objet, mais peuvent devenir temporairement propriétaire - ie le temps d'utiliser cette ressource)

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 611
    Points
    30 611
    Par défaut
    Citation Envoyé par mintho carmo Voir le message
    Je trouve que ces 2 derniers points sont incomplets.

    A mon avis :
    • un seul propriétaire et un seul observateur (le propriétaire) ==> unique_ptr
    • plusieurs propriétaire OU pluisuers observateur ==> shared_ptr


    Par défaut, on doit considérer que l'on est en multithreads (soit parce que c'est effectivement le cas, soit parce que rien n'interdit dans le design que ça ne sera pas le cas dans le futur). Dans cette situation, avec unique_ptr, pour avoir un observateur, on peut passer :

    • par un pointeur nu ==> impossible de vérifier si l'objet est détruit ou non
    • une référence ou un équivalent de weak_ptr ==> impossible de garantir que l'objet n'est pas détruit entre le moment où l'on teste la validité de l'objet et le moment où on l'utilise


    Dans les 2 cas, le design est problématique

    Le seul moyen sécurisé d'avoir plusieurs observateurs est d'utiliser shared_ptr/weak_ptr. Le premier est utilisé pour avoir plusieurs propriétaire de la ressource (situation qui devrait reste rare), le second pour avoir plusieurs observateurs sur la ressource. Lorsqu'un observateur souhaite utiliser la ressource, il doit localement promouvoir le weak_ptr en shared_ptr (et dans ce cas, le shared_ptr est détruit à la fin du bloc, l'observateur est juste un propriétaire temporaire)

    Donc à mon sens, le cas 4 correspond à unique_ptr si pas d'autre observateur que le propriétaire ou shared_ptr/weak_ptr s'il y a plusieurs observateurs
    Hé bien, je ne suis pas du tout d'accord avec ton raisonnement...

    A partir du moment où tu as déterminé qui était le seul responsable de la durée de vie de tes objets, tu veilles simplement à ce qu'il s'occupe de cette gestion en dehors de tout thread enfant (ou plutôt, au niveau du thread principal), et point-barre.

    Si d'autres threads ont besoin de manipuler ces objets, tu le transmets par référence ( *(myUniqu.get() ), de préférence constante ou tu indique clairement dans les spécifications qu'il ne faut jamais faire un delete sur un pointeur nu (car la durée de vie de l'objet pointé est gérée par ailleurs grâce à une capsule RAII).

    Finalement, la capsule RAII n'a qu'un seul et unique objectif : s'assurer que l'objet pointé est correctement détruit avant de perdre toute référence dessus. Rien n'empêche de récupérer un pointeur nu lorsque tu as affaire à une fonction et / ou à un thread qui n'a pas le droit à la parole en ce qui concerne la destruction de l'objet
    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

  19. #19
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    393
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 393
    Points : 684
    Points
    684
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Hé bien, je ne suis pas du tout d'accord avec ton raisonnement...

    A partir du moment où tu as déterminé qui était le seul responsable de la durée de vie de tes objets, tu veilles simplement à ce qu'il s'occupe de cette gestion en dehors de tout thread enfant (ou plutôt, au niveau du thread principal), et point-barre.

    Si d'autres threads ont besoin de manipuler ces objets, tu le transmets par référence ( *(myUniqu.get() ), de préférence constante ou tu indique clairement dans les spécifications qu'il ne faut jamais faire un delete sur un pointeur nu (car la durée de vie de l'objet pointé est gérée par ailleurs grâce à une capsule RAII).

    Finalement, la capsule RAII n'a qu'un seul et unique objectif : s'assurer que l'objet pointé est correctement détruit avant de perdre toute référence dessus. Rien n'empêche de récupérer un pointeur nu lorsque tu as affaire à une fonction et / ou à un thread qui n'a pas le droit à la parole en ce qui concerne la destruction de l'objet
    Par référence ou pointeur, la problématique est la même (et de toute façon, un observateur n'a jamais le droit de faire un delete, sinon ce n'est plus un observateur, mais un propriétaire) : rien ne permet de savoir au propriétaire que la ressource est encore utilisée ou non par un observateur.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    void use_ressource(Ressource* p) { // ou & ou const&
        // use p
    }
     
    std::unique_ptr<Ressource> p = make_ressource();
    std::thread t(ressource, p.get());
    // quand détruire la ressource ?
    On en revient à ce que je disais à Médinoc : si la durée de vie des ressources est supérieure à celle des threads, pas de problème. Mais dans le cas général (c'est pas une situation exception, on peut par exemple considérer un thread graphique qui affichent des ressources gérées par un moteur de rendu), on a un problème de sécurité

    Une fonction ou un thread qui ont accès à une ressource ont forcement leur mot à dire concernant la destruction de cette ressource, même s'ils ne sont pas propriétaire de cette ressource (ie pas resposable de la détruire). Tout simplement parce que s'ils ont accès à une ressource, c'est bien pour l'utiliser à un moment donné et qu'il faut bien que la ressource ne soit pas libérée lorsqu'ils l'utiliseront

  20. #20
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 186
    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 186
    Points : 17 126
    Points
    17 126
    Par défaut
    C'est là que le propriétaire est prié de faire attention.
    S'il y a observateur, il y a observé.

    Le propriétaire du pointeur sait quand un observateur commence à regarder, et ne dois pas libérer avant.
    Par contre, ça veut aussi dire que l'observateur doit signaler qu'il a fini de regarder.

    L'avantage d'une référence sur un pointeur nu, c'est que non seulement l'observateur n'aura pas le droit moral de faire un delete, mais n'en aura pas non plus la possibilité.

    Sauf à être ultra crade delete(&reference), mais on ne peut ps coder contre un tel développeur..
    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. Réponses: 19
    Dernier message: 29/09/2014, 18h12
  2. Réponses: 2
    Dernier message: 04/08/2014, 14h43
  3. Exceptions, quand les utiliser
    Par JuTs dans le forum Général Dotnet
    Réponses: 6
    Dernier message: 13/05/2007, 16h27
  4. [Smarty] Utilité ? Quand les utiliser ?
    Par Xunil dans le forum Bibliothèques et frameworks
    Réponses: 25
    Dernier message: 28/11/2006, 18h08
  5. fonctions et classes... quand les utiliser ?
    Par fastmanu dans le forum Langage
    Réponses: 6
    Dernier message: 03/04/2006, 01h39

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