Dans certains cas, je me suis rendu compte que std::unique_ptr privait de certaines libertés. En effet dans une partie de mon code je me suis retrouvé dans l'embarras lorsque je donne un std::unique_ptr à un objet et que je souhaite le remanipuler après.
Ce post est donc plus une problématique / débat de l'utilisation des smart pointers dans les choix d'architecture. Pour illustrer le problème je vais montrer l'utilisation de ceux ci dans un cas assez simple :
Un fenêtre (window) possède un arrangement (layout) qui possède des éléments graphiques (widget).
Évidemment, le layout et les widgets sont des objets polymorphiques.
Cas 1 : les std::unique_ptr
La solution la plus simple et naïve est de passer des std::unique_ptr au layout.
Window w;
auto l = make_unique<GridLayout>(3, 2);
l.add(make_unique<Label>("Login"));
l.add(make_unique<Input>());
Avantages :
Ownership clair
API plutôt simple
Inconvénients :
Si je souhaite modifier mon objet Input, je suis obligé de faire un downcast (interdit dans ma convention personnelle)
Je suis obligé de faire des fonctions pour retrouver mon widget
Cas 2 : les std::shared_ptr
Le code est assez similaire, sauf que je peux garder les objets concrets si besoin
Window w;
auto l = make_shared<GridLayout>(3, 2);
auto input = make_shared<Input>();
l.add(make_shared<Label>("Login"));
l.add(input);
// plus tard...
input->clear();
Avantages :
Possibilité de garder les types concrets côté utilisateur
Inconvénients :
Surcoût léger lié à l'utilisation des shared_ptr
La sémantique d'objet partagée est inadaptée dans ce contexte
Cas 3 : aucun ownership
Dans ce dernier cas, l'utilisateur est obligé de stocker tous les objets de son côté et les référencer dans le layout.
Possibilité de garder les types concrets côté utilisateur
Moins de responsabilités côté layout/window
Inconvénients :
Pour chaque "fenêtre" il sera nécessaire de faire un objet contenant tous les widgets internes
Ownership étrange, le layout référence des widgets sans les posséder
Objets très statique, déplacer un widget d'une fenêtre à l'autre n'est pas pratique
Conclusion personnelle : J'aime bien la méthode 3. Elle évite pas mal d'allocations dynamiques, elle enlève pas mal de responsabilités au niveau du layout et de la window. Dans Boost il n'est pas rare que des objets prennent des références non-const pour indiquer à l'utilisateur qu'il est nécessaire que son objet soit toujours valide la durée du service.
Que feriez-vous ? Avez vous fait d'autres choix / design ?
- Edité par markand 8 novembre 2018 à 9:28:43
git is great because Linus did it, mercurial is better because he didn't.
Je pense que le problème est insuffisamment spécifié pour que la réponse puisse être définitive. Typiquement, je ne suis pas d'accord avec ton analyse pour la première situation. On peut tout à fait garder une information typée du côté de l'appelant :
auto l = make_unique<GridLayout>(3, 2);
auto input = make_unique<Input>() ;
Input* i = input.get();
l.add(make_unique<Label>("Login"));
l.add(std::move(input));
(On pourrait même imaginer faire de add une fonctionnalité template qui transmet automatiquement l'accès non-responsable exactement typé vers l'extérieur en échange de sa prise de possession).
Mais sinon la solution 3 est également une solution tout à fait pertinente.
Je pense qu'en réalité, il faudrait aller plus loin dans le design 3. L'objet qui "stocke" les informations typées est celui qui est vraiment intéressant en fait. Il n'y a pas de problème d'ownership bizarre. Elle est même bien plus pertinente. Le layout n'a pas à être responsable des widgets, il ne fait qu'organiser leurs positions les uns par rapport aux autres. Et si on veut changer le layout, pour positionner les éléments différemment, il n'y a aucune raison que la destruction de l'ancien layout entraîne la destruction des widgets.
Oui ce que tu proposes est effectivement faisable en gardant l'objet en pointeur brut. Par contre ça nécessite de faire extrêment attention côté utilisateur à ce que le layout ne supprime pas le widget de lui même.
Merci pour ton avis, je vais expérimenter la solution 3 dans divers partie du code pour voir comment ça pourrait me convenir en étoffant.
- Edité par markand 8 novembre 2018 à 9:56:27
git is great because Linus did it, mercurial is better because he didn't.
Ownership dans le choix d'architecture
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
git is great because Linus did it, mercurial is better because he didn't.
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C
git is great because Linus did it, mercurial is better because he didn't.