1. #1
    Nouveau membre du Club
    Homme Profil pro
    Etudiant
    Inscrit en
    janvier 2016
    Messages
    37
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 21
    Localisation : France, Sarthe (Pays de la Loire)

    Informations professionnelles :
    Activité : Etudiant

    Informations forums :
    Inscription : janvier 2016
    Messages : 37
    Points : 30
    Points
    30

    Par défaut Classes et pointeurs intelligents

    Bonjour à tous,

    Actuellement sur un petit développement Qt, je suis contraint de me plonger dans les pointeurs intelligents.

    Et bien qu'ayant parcouru beaucoup de posts sur le sujet, je ne trouve pas d'explications qui me conviennent.

    J'aurais donc besoin de quelques réponses à ce sujet.
    Mais avant voici le contexte :
    - J'utilise une bibliothèque de lecture de carte RFID
    - Quelques lignes pour vous "expliquer" un peu comment fonctionne la chose :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    std::shared_ptr<logicalaccess::ReaderConfiguration> readerConfig(new logicalaccess::ReaderConfiguration());
     
    readerConfig->setReaderProvider(logicalaccess::LibraryManager::getInstance()->getReaderProvider("PCSC"));
     
    const logicalaccess::ReaderList readerList = readerConfig->getReaderProvider()->getReaderList();
     
    readerConfig->setReaderUnit(readerList.at(2));
     
    std::shared_ptr<logicalaccess::Chip> chip = readerConfig->getReaderUnit()->getSingleChip();
    Donc - un readerConfig qui "contient" un ReaderProvider et un ReaderUnit (définit en fonction du dit ReaderProvider)

    - et une carte


    Je me pose les questions suivantes :
    --> Si je souhaite stocker mon ReaderConfig et ma carte dans une classe, dois-je le stocker sous le type logicalaccess::ReaderConfiguration ou std::shared_ptr<logicalaccess::ReaderConfiguration> ?

    --> Si je souhaite l'utiliser dans une autre classe que celle où je l'ai définit, il me faudra d'abord créer une instance de la classe et utiliser un getter de cette classe

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    Reader *reader = new Reader;
    "MonNouveauReaderConfig" = reader->getCurrentReaderConfig();
    Si "MonNouveauReaderConfig" est un shared_ptr, je ne peut pas simplement lui retourner mon premier shared_ptr. Comment faire ?
    J'ai pris conaissance de la fonction make_shared mais je peine à comprendre comment l'utiliser...


    --> Si j'assigne un ReaderUnit à mon premier shared_ptr de cette façon :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    readerConfig->setReaderUnit(blablabla);
    Est-ce que "MonNouveauReaderConfig" de mon autre classe possèdera également ce ReaderUnit que je viens de lui assigner ?


    Pourquoi l'utilisation d'un new pour déclarer readerConfig alors que la carte est définit grâce à une fonction contenu dans la classe ReaderConfig ? Cela fait-il une différence car lors de mes expérimentations sur make_shared, j'ai pu simplement l'appliquer sur readerConfig, mais pas sur chip.

    Voilà voilà.... ça fait beaucoup de questions mais elles sont assez basiques je pense. J'ai seulement du mal à comprendre les shared_ptr dans leur globalité et leur utilisation dans des classes étant nouveau pour moi, ça n'arrange pas les choses.

    Merci d'avance à tout ceux qui sauront m'apporter des réponses, ou même une simple piste

  2. #2
    Expert éminent
    Homme Profil pro
    Développeur informatique
    Inscrit en
    février 2005
    Messages
    4 424
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France, Val de Marne (Île de France)

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

    Informations forums :
    Inscription : février 2005
    Messages : 4 424
    Points : 9 894
    Points
    9 894

    Par défaut

    Actuellement sur un petit développement Qt, je suis contraint de me plonger dans les pointeurs intelligents.
    Attention, les pointeurs "à la Qt" et les pointeurs intelligents sont hautement incompatible.
    Ne tentez pas d'utiliser des pointeurs intelligents sur des types de Qt.

    En n'utilisant correctement, vous n'utilisez JAMAIS "new".
    A la place de vos new, utilisez "make_shared".

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    std::shared_ptr<logicalaccess::ReaderConfiguration> readerConfig(std::make_shared<logicalaccess::ReaderConfiguration>());
    --> Si je souhaite stocker mon ReaderConfig et ma carte dans une classe, dois-je le stocker sous le type logicalaccess::ReaderConfiguration ou std::shared_ptr<logicalaccess::ReaderConfiguration> ?
    En "std::shared_ptr<logicalaccess::ReaderConfiguration>" car il est très peu probable que vous disposiez d'un constructeur de copie public dans la classe "logicalaccess::ReaderConfiguration".

    --> Si je souhaite l'utiliser dans une autre classe que celle où je l'ai définit, il me faudra d'abord créer une instance de la classe et utiliser un getter de cette classe
    Non.

    Est-ce que "MonNouveauReaderConfig" de mon autre classe possèdera également ce ReaderUnit que je viens de lui assigner ?
    Oui, c'est une sémantique de pointeur.

    Pourquoi l'utilisation d'un new pour déclarer readerConfig
    C'est une erreur, il faut utiliser "make_shared".

    J'ai seulement du mal à comprendre les shared_ptr dans leur globalité et leur utilisation dans des classes étant nouveau pour moi, ça n'arrange pas les choses.
    L'intérêt des pointeurs intelligents, c'est de correctement gérer "l'ownership" des pointeurs : qui est en charge d'appeler le "delete" de l'objet pointé.
    Avec "shared_ptr", tous ceux qui possèdent un "shared_ptr" sur un objet en sont les copropriétaires.
    C'est à la destruction/libération du dernier "shared_ptr" sur l'objet que celui-ci est détruit.

  3. #3
    Nouveau membre du Club
    Homme Profil pro
    Etudiant
    Inscrit en
    janvier 2016
    Messages
    37
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 21
    Localisation : France, Sarthe (Pays de la Loire)

    Informations professionnelles :
    Activité : Etudiant

    Informations forums :
    Inscription : janvier 2016
    Messages : 37
    Points : 30
    Points
    30

    Par défaut

    Une réponse rapide et précise... Merci !


    Je n'avais pas pris connaissance des pointeurs Qt... J'ai cependant eu la chance de ne pas tomber sur des pointeurs de type Qt.
    Je garde tout de même ceci dans un coin de ma tête. Quelque chose me dit que ça me sera utile sous peu.




    Je ne sais pas ce qu'est un constructeur de copie mais, effectivement, rien ne semble effectuer la "conversion" dans la classe ReaderConfiguration.




    Un simple "make_shared" dans la seconde classe suffit donc ?
    Reprenez moi si je me trompe mais cela signifie-t-il que, dans les attributs privés de la classe 1 ou dans une fonction de la classe 2, les deux "déclarations" sont des "copropriétaires" d'un objet ReaderConfiguration qui est créé lors de la première déclaration d'un shared_ptr ?

    Au premier abord, je voyez ça comme ça :
    le premier shared_ptr est créé dans les attributs de la classe 1
    on instancie la classe 1 afin d'avoir toujours un shared_ptr empêchant la destruction
    on effectue une copie du premier shared_ptr pour l'utiliser
    on crée des "copropriétaires" lors d'utilisations dans les fonctions d'autres classes

    Je me rend bien compte que ce raisonnement manque de logique et qu'il est plein d'incohérences, cependant je me perd quant à la portée des pointeurs :
    je sais bien qu'ils persistent tant qu'il en reste au minimum 1 comme tu l'as très bien expliqué plus haut mais pour un pointeur dans les attributs d'une classe, si la classe n'est pas instancié, il ne devrait pas exister, si ?)

    Ou alors, je ne dois pas le déclarer dans les attributs mais la première déclaration se fera alors dans une fonction et, à la fin de la scope, la seul copie de l'objet sera supprimée et l'objet avec lui. Je ne pourrais donc pas le récupérer dans un autre endroit de mon programme...




    Le new provient d'un précédent programme que j'avais effectué sous Visual Studio. Je l'ai bêtement copié sans trop réfléchir mais je veillerai alors à faire disparaître toute ces immondicités !


    Encore pas mal de questions mais je pense que je commence à voir la lumière dans tout ça !

  4. #4
    Expert éminent
    Homme Profil pro
    Développeur informatique
    Inscrit en
    février 2005
    Messages
    4 424
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France, Val de Marne (Île de France)

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

    Informations forums :
    Inscription : février 2005
    Messages : 4 424
    Points : 9 894
    Points
    9 894

    Par défaut

    Je ne sais pas ce qu'est un constructeur de copie mais, effectivement, rien ne semble effectuer la "conversion" dans la classe ReaderConfiguration.
    https://cpp.developpez.com/faq/cpp/?...cteur-de-copie

    Un simple "make_shared" dans la seconde classe suffit donc ?
    Pas besoin de faire plusieurs "make_shared", il faut même éviter.

    "make_shared", c'est en gros, un new avec tout ce qu'il faut autour pour gérer des erreurs dans les constructeurs.
    Donc appeler plusieurs fois "make_shared", c'est créer plusieurs objets différents et donc pas de partages de propriétés.

    Un "std::shared_ptr" a un constructeur de copie, il est donc très facilement clonable et celui qui obtient un clone du "std::shared_ptr" devient un copropriétaire de l'objet pointé par le "std::shared_ptr" initial (et aussi par le clone, aussi bien que par les clones du clone, etc...). Donc l'objet créé est toujours valide tant qu'il est accessible via un "std::shared_ptr" (clone ou pas).

    Reprenez moi si je me trompe mais cela signifie-t-il que, dans les attributs privés de la classe 1 ou dans une fonction de la classe 2, les deux "déclarations" sont des "copropriétaires" d'un objet ReaderConfiguration qui est créé lors de la première déclaration d'un shared_ptr ?
    Non, vous avez 2 objets, chacun propriété soit de l'objet de type classe1 soit de la fonction de la classe 2.
    Si le pointeur est une variable locale de la fonction de la classe 2, il sera libéré à la fin de la méthode, et comme il est le seul propriétaire de l'objet pointé (vu qu'il n'a pas été cloné), l'objet pointé sera libéré en fin de méthode.

    le premier shared_ptr est créé dans les attributs de la classe 1
    OK, on parle bien de champs non statiques de la classe 1 ?

    on instancie la classe 1 afin d'avoir toujours un shared_ptr empêchant la destruction
    La destruction de quoi ?
    Tant que vous laissez un shared_ptr sur un objet, il ne sera pas libéré. Mais si l'objet contenant un champ de type shared_ptr est libéré, et que ce shared_ptr est le dernier sur l'objet pointé, l'objet pointé sera aussi libéré, c'est toute l'élégance des shared_ptr.

    on effectue une copie du premier shared_ptr pour l'utiliser
    Pas besoin, vous pouvez utiliser l'original, il s'use pas quand on s'en sert.

    on crée des "copropriétaires" lors d'utilisations dans les fonctions d'autres classes
    Quand vous passez un shared_ptr en paramètre, vous passez un clone. Quand vous affectez un champ de type shared_ptr depuis un autre, vous clonez l'autre, etc...
    La copropriété, c'est totomatique.

    cependant je me perd quant à la portée des pointeurs :
    Je pense que vous êtes encore trop près des pointeurs nus dans votre manière de penser les pointeurs.

    mais pour un pointeur dans les attributs d'une classe, si la classe n'est pas instancié, il ne devrait pas exister, si ?)
    Vous parlez d'un champ statique ou d'un champ d'instance ?
    Si c'est un champ statique, c'est à vous de l'instancier.
    Si c'est un champ d'objet, il est initialisé à pointer sur nullptr, donc pas de problème lors de sa destruction.

    Ou alors, je ne dois pas le déclarer dans les attributs mais la première déclaration se fera alors dans une fonction et, à la fin de la scope, la seul copie de l'objet sera supprimée et l'objet avec lui. Je ne pourrais donc pas le récupérer dans un autre endroit de mon programme...
    ???
    Le plus simple, ne serait-il pas de le fournir dans les paramètres du constructeur, ou de l'initialiser dans le constructeur ???

  5. #5
    Nouveau membre du Club
    Homme Profil pro
    Etudiant
    Inscrit en
    janvier 2016
    Messages
    37
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 21
    Localisation : France, Sarthe (Pays de la Loire)

    Informations professionnelles :
    Activité : Etudiant

    Informations forums :
    Inscription : janvier 2016
    Messages : 37
    Points : 30
    Points
    30

    Par défaut

    Pas besoin de faire plusieurs "make_shared", il faut même éviter.

    "make_shared", c'est en gros, un new avec tout ce qu'il faut autour pour gérer des erreurs dans les constructeurs.
    Donc appeler plusieurs fois "make_shared", c'est créer plusieurs objets différents et donc pas de partages de propriétés.
    Un seul make_shared "initial" donc, puis une copie du premier à chaque réutilisation.

    Un "std::shared_ptr" a un constructeur de copie, il est donc très facilement clonable et celui qui obtient un clone du "std::shared_ptr" devient un copropriétaire de l'objet pointé par le "std::shared_ptr" initial (et aussi par le clone, aussi bien que par les clones du clone, etc...). Donc l'objet créé est toujours valide tant qu'il est accessible via un "std::shared_ptr" (clone ou pas).
    Ok, je pense avoir bien assimilé cette partie là


    OK, on parle bien de champs non statiques de la classe 1 ?
    Oui, je ne travaille pour le moment pas avec du static. M'a-t-on dit que c'était dangereux de l'utiliser à tout azimuts et, bien que je comprenne le fonctionnement global, je ne pense pas encore saisir son intérêt assez concrètement pour comprendre quand l'utiliser ou pas.


    Pas besoin, vous pouvez utiliser l'original, il s'use pas quand on s'en sert.
    Ca m'embêterait qu'ils commencent à disparaître aléatoirement ! J'ai déjà assez de mal comme ça !


    Quand vous passez un shared_ptr en paramètre, vous passez un clone. Quand vous affectez un champ de type shared_ptr depuis un autre, vous clonez l'autre, etc...
    La copropriété, c'est totomatique.
    Le constructeur paraît donc parfait pour mon cas où je souhaite y avoir accès tout au long du programme, effectivement. J'aurais un clone du premier et pourrait gérer l'objet pointé dans toute les fonctions de la classe.


    Je pense que vous êtes encore trop près des pointeurs nus dans votre manière de penser les pointeurs.
    Effectivement, je n'ai pas encore bien apprivoisé les pointeurs intelligents. Je pense cependant avoir fait un bon tour de ce qui se trouve sur internet et que le problème provient plus d'un manque de pratique et d'expérimentations auxquelles me raccrocher. Je manque malheureusement de temps pour me pencher dessus comme il le faudrait/je le voudrais.


    Vous parlez d'un champ statique ou d'un champ d'instance ?
    Si c'est un champ statique, c'est à vous de l'instancier.
    Si c'est un champ d'objet, il est initialisé à pointer sur nullptr, donc pas de problème lors de sa destruction.
    Ok. J'ai donc tout un tas de nullptr un peu partout à fixer...


    ???
    Le plus simple, ne serait-il pas de le fournir dans les paramètres du constructeur, ou de l'initialiser dans le constructeur ???
    Effectivement... Ça paraît tout bête mais je n'y avais pas pensé !

    Si j'essaye de suivre le raisonnement jusqu'ici, dans le cas d'un programme Qt où je souhaite pouvoir utiliser mon objet pointé tout au long du programme :
    Le make_shared devrait se faire dans le main afin de toujours garder l'objet "en vie"

    Mettre un shared_ptr dans le constructeur d'une classe afin de faire une copie.
    La question se pose donc de "quand la classe est-elle instanciée ?"

    Mes hypothèses :
    Soit Elle l'est de base mais ça me paraît assez irréaliste et je ne vois pas comment le constructeur irait chercher le shared_ptr initial. On aurait donc un pointeur vers nullptr mais je pense qu'on peut laisser cette hypothèse de côté.

    Soit Elle l'est grâce au qmlRegisterType qui me permet de créer un objet dans mon main.qml auquel cas je ne vois pas comment lui passer en paramètre le shared_ptr initial car je ne fais aucun appel au constructeur directement ; simplement un

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    main.cpp
    qmlRegisterType<Reader>("Reader", 1, 0, "Reader");
     
    main.qml
    Reader
            {
                id:reader
            }
    Soit il me faut l'instancier, peut-être dans le main si je souhaite récupérer mon pointeur initial. Dans ce cas, je suis perturbé par le QObject *parent = nullptr qui se trouve en paramètre dans le constructeur de mes classes. Il semblerait qu'il n'apprécie pas trop que je touche aux constructeurs

    Merci encore du temps que tu me donnes et désolé de poser autant de question mais j'aimerais vraiment comprendre et chaque réponse me pousse vers une montagne d'autres questions.
    Je remarque au final que je connait plein de choses mais que je n'en comprend pas bien le fonctionnement.

  6. #6
    Expert éminent
    Homme Profil pro
    Développeur informatique
    Inscrit en
    février 2005
    Messages
    4 424
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France, Val de Marne (Île de France)

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

    Informations forums :
    Inscription : février 2005
    Messages : 4 424
    Points : 9 894
    Points
    9 894

    Par défaut

    Ok. J'ai donc tout un tas de nullptr un peu partout à fixer...
    Un avantage des pointeurs intelligent, c'est qu'ils s'initialisent automatiquement à nullptr sans qu'on ait à le dire et ils gèrent très bien qu'ils sont "initialisés" à nullptr.

    Le make_shared devrait se faire dans le main afin de toujours garder l'objet "en vie"
    Le but d'un shared_ptr n'est pas de conserver par devers soi un objet en vie, c'est de partager la propriété avec plusieurs objets.
    Si, dans le main, vous créez un shared_ptr et que vous le passé en paramètres des constructeurs de tous les objets qui en aurait besoin, vous n'avez plus à conserver ce shared_ptr dans le main.
    Chaque objet qui a conservé un clone du shared_ptr est copropriétaire de l'objet pointé et que le main ne conserve pas le shared_ptr "original" n'a aucun impact sur la vie de l'objet pointé.

    La question se pose donc de "quand la classe est-elle instanciée ?"
    Question étrange, car il s'agit d'une question fondamentale qu'un débutant en POO se pose mais vous ne semblez pas être un débutant.
    L'instanciation d'une classe donne un objet. Cela arrive quand on déclare une variable locale (ou globale mais c'est caca) de ce type ou quand on appelle l'opérateur "new" avec le nom de la classe avec les éventuels paramètres nécessaires au constructeur de la classe.

    Soit Elle l'est de base mais ça me paraît assez irréaliste et je ne vois pas comment le constructeur irait chercher le shared_ptr initial.
    Je comprends pas. Il suffit qu'il soit passé en argument du constructeur.

    On aurait donc un pointeur vers nullptr mais je pense qu'on peut laisser cette hypothèse de côté.
    En utilisant la liste d'initialisation, le champ de type shared_ptr<XXX> sera directement initialisé à la valeur passée en paramètre (utilisation du constructeur de copie de shared_ptr).

    Soit Elle l'est grâce au qmlRegisterType
    Oulà, on sort complètement des pointeurs intelligents et on s'approche dangereusement des mécanismes internes de Qt peu compatibles avec les pointeurs intelligents.

    Il semblerait qu'il n'apprécie pas trop que je touche aux constructeurs
    Normal, on n'est dans un contexte "Qt-Object", pas des objets standards, donc on oublie les pointeurs intelligents dans ce contexte.

  7. #7
    Nouveau membre du Club
    Homme Profil pro
    Etudiant
    Inscrit en
    janvier 2016
    Messages
    37
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 21
    Localisation : France, Sarthe (Pays de la Loire)

    Informations professionnelles :
    Activité : Etudiant

    Informations forums :
    Inscription : janvier 2016
    Messages : 37
    Points : 30
    Points
    30

    Par défaut

    Citation Envoyé par bacelar Voir le message
    Si, dans le main, vous créez un shared_ptr et que vous le passez en paramètre des constructeurs de tous les objets qui en aurait besoin, vous n'avez plus à conserver ce shared_ptr dans le main.
    Chaque objet qui a conservé un clone du shared_ptr est copropriétaire de l'objet pointé et que le main ne conserve pas le shared_ptr "original" n'a aucun impact sur la vie de l'objet pointé.
    C'est vrai, cela suffirait !


    Question étrange, car il s'agit d'une question fondamentale qu'un débutant en POO se pose mais vous ne semblez pas être un débutant.
    L'instanciation d'une classe donne un objet. Cela arrive quand on déclare une variable locale (ou globale mais c'est caca) de ce type ou quand on appelle l'opérateur "new" avec le nom de la classe avec les éventuels paramètres nécessaires au constructeur de la classe.
    Il ne faut pas se fier aux apparences, ou disons que cela dépend de ce qu'on entend par débutant

    Je n''instancie aucune classe côté C++ justement. mais j'arrive à en utiliser les propriétés et attributs dans le QML grâce à QProperty et QInvokable. Je pense donc que je dois les instancier seulement en tant qu'objet QML (ou un truc du genre) mais pas côté C++. Mais on s'éloigne des pointeurs intelligents comme vous l'avez dit plus bas !


    En utilisant la liste d'initialisation, le champ de type shared_ptr<XXX> sera directement initialisé à la valeur passée en paramètre (utilisation du constructeur de copie de shared_ptr).
    OK !


    Oulà, on sort complètement des pointeurs intelligents et on s'approche dangereusement des mécanismes internes de Qt peu compatibles avec les pointeurs intelligents.
    J'en conclut qu'un objet QML est différent d'une instance de classe en C++.




    Normal, on n'est dans un contexte "Qt-Object", pas des objets standards, donc on oublie les pointeurs intelligents dans ce contexte.
    Okay ! J'entrevois un certain nombre de possibilités maintenant grâce à toi !

    Je pense prendre le temps de toute les tester et de voir comment réagit le Debugger et quel genre d'atrocités il va me souligner !

    Je pense notamment me pencher sur des constructeurs secondaires, qui me paraît être la solution la plus adaptée.

    A voir ensuite si je ce ne serais pas plus intéressant de recommencer tout le programme en structurant un peu mieux son architecture (c'est un bordel comme j'en ai jamais produit !) et en des classes un peu plus représentatives et qui sépareraient mieux la partie DATA et la partie VIEW (donc classe d'objet QML et objet C++, si j'ai bien suivi !)

    Je me pencherais ensuite sur des solutions moins "conventionnelles" et un peu plus "sales" pour voir jusqu'où je peux abuser.
    Je pense, typiquement, à passer mon shared_ptr à travers le QML en utilisant un pointeur normal que je "garderai en vie" autant que possible et que je récupèrerai de l'autre côté dans une autre classe.

    Ou me pencher sur les QSharedPtr que je n'ai pu que rapidement survoler ! Je n'ai pour le moment aucune idée de si une piste est à explorer de ce côté mais cette classe doit bien savoir à quelque chose non ?

    Je pense que j'ai désormais un assez grand nombre d'éléments pour rechercher de mon côté.
    Un gros à toi en tout cas ! Ça m'a beaucoup aidé et j'y vois bien plus clair sur un grand nombre de choses !

  8. #8
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    Consultant informatique
    Inscrit en
    octobre 2004
    Messages
    10 599
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : Belgique

    Informations professionnelles :
    Activité : Consultant informatique

    Informations forums :
    Inscription : octobre 2004
    Messages : 10 599
    Points : 23 554
    Points
    23 554

    Par défaut

    Salut,

    Attention! Si tu en es toujours sur le problème que tu exposais dans cette discussion (héhé, tu croyais que je ne te reconnaîtrais pas, peut être Coquin, va! ), tu n'as absolument aucun besoin d'avoir recours à un shared_ptr (ni même, de manière générale, à l'allocation dynamique) pour la classe qui contient tes lecteurs de cartes.

    Et je ne suis même pas sur que tu en aies besoin pour tes lecteurs de cartes eux-même! (là, il faudra creuser un peu ). Je m'explique:

    La règle, en C++, c'est de n'avoir recours à l'allocation dynamique de la mémoire (et au pointeurs intelligents par la même occasion) qu'en tout dernier recours; quand tu n'as vraiment pas pas d'autre choix à ta disposition.

    Pourtant, je crois sincèrement que tu as le choix ici, et que tu ne t'es tourné vers l'utilisation de std::shared_ptr que parce que tu t'es dit quelque chose comme "ben, mon ReaderConfiguration va être utilisé par différents widgets, et ce serait dommage qu'il ne soit détruit tant que l'un de ces widgets en a besoin". Me trompés-je

    Or, en suivant ce chemin de pensée, tu as fait une erreur monumentale: tu as confondu les utilisateurs de ta ressources (de ton ReaderConfiguration) et son propriétaire, et, pour que tu ne me demandes pas ce que j'entends par ces deux termes, je vais répondre anticipativement à ta prochaine question:

    • Le propriétaire d'une ressource est l'élément qui a le droit "de vie et de mort" sur la ressource: C'est lui qui décide de la créer, et c'est lui qui décide de la libérer "le moment venu".
    • Par contre, l'utilisateur de cette ressource ne peut -- de toutes manières -- absolument pas décider de libérer la ressource. Il devrait se contenter de savoir qu'il reçoit une ressource qui existe (dont il n'a donc aucune raison de tester l'existence, vu qu'il sait qu'elle existe) et de l'utiliser à sa convenance.

    Du coup, en décidant de transmettre un shared_ptr aux utilisateurs de la ressource, tu leur donne une responsabilité qu'il n'auraient jamais du avoir: celle de décider du moment où la ressource sera libérée.

    Or, il est particulièrement simple de fournir une ressource (quelle qu'elle soit) à un utilisateur de manière telle à ce qu'il n'ai ni la possibilité de la libérer, ni le besoin d'en tester l'existence, car il pourra partir du principe que "si je l'ai reçue, c'est qu'elle existe".

    Ce moyen n'est rien d'autre que la notion de référence, car ce qui est référencé par une référence doit exister au moment où la référence est créée pour être transmise. Tu n'as donc à t'assurer que ce qui est référencé continuera bel et bien à exister aussi longtemps que "ce à quoi tu l'as transmis" en aura besoin.

    Et là, l'ordre de création et de destruction des variable va nous venir en aide. Il faut en effet savoir que les variables sont créées dans l'ordre de leur déclaration, mais qu'elles sont détruite dans l'ordre inverse. Ainsi voilà en gros comment cela se passe:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    int main(){
        DataType data; // data est créé en premier
        /* on considère que data est transmis par référence */
        Type myObject(data); // myObject est créé en second
    } // myObject est détruit en premier
      // et ce n'est seulement qu'après que data sera détruit
    Et bien sur, il en va exactement de même pour les données membres d'une classe!

    Et la chose qui ne gâche rien: tu peux tout à fait déclarer une donnée membre de classe qui soit... une référence sur un objet existant.

    Du coup, pour autant que ton ReaderConfiguration soit déclaré en premier (ou, en tout état de cause, avant tout élément qui le recevrait sous forme de référence), tu n'as pas à t'en faire : il ne sera détruit qu'après les éléments qui en ont besoin (*)

    Quant aux destructeurs de classes, il vont:
    1. effectuer ce qu'il y a dans leur corps
    2. détruire les données membres (pour lesquelles on n'a pas eu recours à l'allocation dynamique de la mémoire) dans l'ordre inverse de leur création
    3. appeler le destructeur des classes de base, dans l'ordre inverse des déclaration d'héritage


    Ainsi, vu que tu travailles avec Qt, tu pourrais très bien envisager de créer un widget personnel dont la forme serait proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    class MyWidget : public QWidget{
        Q_OBJECT
    public:
        MyWidget(ReaderConfiguration &, QWidget * parent = nullptr);
    private:
        /* c'est une référence : le compilateur sait qu'il ne doit pas la détruire */
        ReaderConfiguration & reader;
        QLineEdit * myEdit;
        QCombobox * myCombo;
        /* ... */
    }
    avec l'implémentation du constructeur prenant la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /* Quand une donnée membre est une référence, nous n'avons
     * pas d'autre choix que de la définir dans la liste d'initialisation
     *
     * Mais, avant même de définir cette référence, il faut créer la classe parent
     */
    MyWidget::MyWidget(ReaderConfiguration &, QWidget * parent = nullptr):
        QWidget(parent), reader{reader}{
        /* et, dans le corps du constructeur, on fait joujou avec
         * les éléments graphiques myEdit et myCombo
         */
    }
    (Notes que tu pourrais faire dix classes sur le même schéma, cela irait toujours tout aussi bien )

    Notes au passage qu'un utilisateur peut très bien transmettre la ressource utilisée à un autre utilisateur qu'il lui faudrait créer.

    Ainsi, si tu rajoutes un bouton à ton widget et que tu le connecte a un slot (on l'appellera "onButtonClick" par facilité ) qui ouvre une boite de dialogue (que l'on appellera MyDialogBox, par facilité) qui a besoin de reader pour travailler, tu peux très bien te retrouver avec un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void MyWidget::onButtonClick(){
        /* la boite de dialogue doit être détruite quand on quitte la fonction
         * ==> pas d'allocation dynamique de la mémoire
         */
        MyDialog dialog(reader);
        if(dialog.exec()){
            /* tout ce qu'il faut faire si l'utilisateur a cliqué sur le bouton 
             * "Ok" de la boite de dialogue
             */
        }
    }
    Et, bien sur, tu créeras ta fenêtre principale qui semble être le candidat idéal pour devenir le propriétaire de ton ReaderConfiguration. Après tout: elle est déjà la propriétaire de tous les widgets qui seront créés, même si c'est de manière indirecte, à cause du système parent / enfant de Qt. Elle peut très bien être la propriétaire "d'un élément de plus", non

    Du coup, elle prendra sans doute une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class MainWindow: public QMainWindow{
        Q_OBJECT
    public:
         MainWindow();
    private:
        ReaderConfiguration reader;
        MyWidget * myWidget;
        /* tous les autres widgets dont tu as besoin */
    };
    et son constructeur prendrait la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    MainWindow::MainWindow(): reader{/* paramètres éventuels*/ }{
        /* Quand on arrive ici, reader existe déjà...
         * on peut donc l'utiliser à notre guise , par exemple
         */
        myWidget = new MyWidget{reader};
        /* on fait joujou avec myWidget et les autres */
    }
    Avoue que c'est tout de suite beaucoup plus simple que de commencer à jouer avec des shared_ptr, non

    (*)Il n'y a que dans un contexte multi-threadé que les choses deviennent peut-être un peu plus complexes
    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. Problèmes avec classes et pointeurs
    Par Anas1984 dans le forum C++
    Réponses: 2
    Dernier message: 02/11/2006, 13h49
  2. Les pointeurs intelligents
    Par MatRem dans le forum C++
    Réponses: 8
    Dernier message: 20/06/2006, 20h27
  3. pointeur intelligent??
    Par yashiro dans le forum C++
    Réponses: 3
    Dernier message: 04/04/2006, 09h08
  4. Pointeur intelligent boost : return NULL ->comment faire?
    Par choinul dans le forum Bibliothèques
    Réponses: 7
    Dernier message: 21/12/2005, 17h24
  5. Classe, pile, pointeurs et casse-tête!
    Par zazaraignée dans le forum Langage
    Réponses: 6
    Dernier message: 26/09/2005, 17h57

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