Citation:
Envoyé par
Jean-Marc.Bourguet
On n'est vraiment pas d'accord. Tout d'abord, on n'a jamais parlé de shared_ptr jusqu'à présent. Mais bon, si tu veux le traiter.
Je ne fais que me re-citer :
Citation:
Mais le fait est que tu ne pourras pas te "limiter" à la seule modification de cette classe, car, autrement, tu vas te retrouver avec des références non comptées pour tes shared_ptr ou avec des classes et des fonctions qui, manque de pot, manipulent justement le pointeur qui fini sous la responsabilité de ton unique_ptr et auxquelles il te sera impossible de signaler que le pointeur qu'elles manipulent est invalidé.
j'aurais peut être du les mettre dans l'autre sens (ben non, les gens se seraient de toutes manière focalisés sur le fait que je parlais de shared_ptr!!), mais si j'ai cité les shared_ptr ici, c'est parce que le problème est, quoi qu'il arrive, similaire : à partir du moment où un pointeur intelligent d'une ressource, il doit prendre cette responsabilité depuis le début, ou la récupérer de pointeurs identiques qui accepteront de la lui remettre, autrement ne sera que problèmes en pagaille!
Citation:
Reprenons les bases. Il y a différentes politiques de gestion de la mémoire. Certaines sont locales, elles ne demandent comme collaboration entre composants participants que l'accord sur la politique à mener et sur la définition l'interfaces, le moyen de la mettre en œuvre reste d'un choix purement local. C'est le cas de la responsabilité non partagée.
La manière dont est géré le pointeur n'implique qu'un choix purement local:
Qu'il le soit au travers d'un std::unique_ptr, d'un boost::unique_ptr ou d'un NIHuniquePtr n'a effectivement d'importance qu'au niveau de la classe qui manipule ce pointeur intelligent, je te l'accorde.
Citation:
D'autres sont globales, elles nécessitent non seulement l'accord sur la politique et l'interface, mais aussi sur le moyen de la mise en œuvre. C'est le cas de la responsabilité partagée.
Mais, là, je ne suis pas d'accord du tout :
que l'on te parle de shared_ptr ou de unique_ptr (quelle que soit leur origine),
dés le moment où un pointeur intelligent, quel qu'il soit, rentre dans la danse, tu dois en tenir compte de manière globale.
autrement, tu te retrouveras toujours soit avec une classe qui ne devrait pas se retrouver avec la responsabilité mais qui essaye de faire un delete sur ton pointeur, soit avec une classe qui continue à manipuler ton pointeur après que le pointeur intelligent ait fait son job.
Dans les deux cas, ce sera l'erreur de segmentation garantie ;)
Citation:
Dans le premier cas, un composant peut décider seul du moyen de mettre en œuvre sa politique, quel que soit son choix pour l'implémentation de la politique, il peut le modifier tant qu'il ne touche pas son interface sans que ça touche les autres. Et s'il touche son interface, ça ne va toucher ses partenaires que dans les alentours immédiats pour reprendre ta formulation. Et c'est vrai pour unique_ptr.
Cela risque d'aller beaucoup plus loin que "les alentours immédiats".
Cela peut toucher strictement n'importe quel classe ou n'importe quelle fonction qui manipule le pointeur en question.
Si, sur les 100 classes dont je parlais là tantôt, tu en as 35 qui manipule un A*, 35 qui manipulent un B* et 30 qui manipulent un C*, si tu fais rentrer un pointeur intelligent (quel que soit son type ou son origine) dans la danse, c'est l'ensemble des 35 (ou des 30) classes qui seront impactées, parce que tu ne peut avoir aucune garantie qu'il n'y aura pas "un chemin indirect oublié de tous" (ou simplement inconnu du développeur, si l'une des classes est développée par "quelqu'un d'autre") qui mènera à l'utilisation du pointeur intelligent.
Citation:
Dans le second choix, une décision de changer le moyen de mettre en œuvre la politique va devoir toucher tout le monde, puisqu'il faut collaborer sur cette mise en œuvre ailleurs qu'aux interfaces.
De manière générale, quel que soit le pointeur intelligent envisagé, même sans l'exposer outre mesure, le fait d'en introduire dans une logique qui ne l'utilisait pas avant aura un impacte généralisé, c'est ce que j'ai toujours dit et c'est ce que je répète encore ici
Citation:
C'est vrai aussi pour unique_ptr, qui était l'unique objet de la discussion jusqu'à présent.
Bien sur que c'est aussi vrai pour unique_ptr, mais, que je sache, je ne l'ai pas exclu de mon raisonnement ;)
Tu as même cité toi meme le passage dans lequel, bien qu'incluant shared_ptr, je parlais de unique_ptr ;)
Citation:
Et mon point est qu'unique_ptr est devenu le moyen standard de gérer la politique "possesseur unique", tout comme std::string est devenu le moyen standard de gérer les chaines de caractères, n'en déplaise à tout ceux qui, Qt en tête puisque tu le citais, gardent leur propre type de chaines pour de plus ou moins bonnes raisons, la compatibilité en est une bonne, l'attachement fétiche tenant de NIH en est une mauvaise.
Sur ce point, je suis on ne peut plus d'accord avec toi ;)
Citation:
Pour les chaines comme pour les pointeurs, quand j'ai de bonnes raisons de les manipuler autrement que de la manière standard, je le fais entre mes sous-composants, je ne l'expose pas dans mon interface. Parce que plus le temps va passer, tout comme il est devenu de plus en plus surprenant de voir des char const* ou des nihString dans les interfaces, de plus il va être surprenant de voir des T* ou des nihUniquePtr<T> dans celles-ci.
Ca, je te l'accorde bien volontiers
Citation:
Les nihCountedPtr<T> vont eux rester plus longtemps, exactement pour la raison que tu donnes, c'est un choix non local et le basculement devra être fait d'un coup et pas par percolation à partir de noyaux précurseurs.
Et je suis même d'accord avec toi sur ce point.
Le point au final sur lequel on n'est pas d'accord, c'est sur le fait que même un std::unique_ptr (pour bien le mettre en opposition avec tous les autres de la même catégorie), ne doit pas s'exposer outre mesure.
Pour moi, ce qu'il doit exposer, si tant est que cela ait sens au niveau des classes qui l'utilisent, ce sont les comportement qui permettent la gestion du pointeur dont il a la charge, et rien de plus.
Citation:
Tu n'as toujours pas montré en quoi unique_ptr ne convenait pas pour ça.
S'il n'y a que ca...
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| void foo(Type * t){
/* pas d'utilisation de std::unique_ptr */
}
void fctQuiUtiliseUniquePtr(Type * t){
std::unique_ptr<Type> ptr(t)
/* ce pourrait tout aussi bien être
void fctQuiUtiliseUniquePtr(std::unique_ptr<Type> ptr){ */
/* manipulation diverses et variées */
} ptr est détruit ici -->delete t est invoqué, t est invalidé
void bar(Type * t){
/* ... */
if(test){
fctQuiUtiliseUniquePtr(t);
/* ou
fctQuiUtiliseUniquePtr(std::unique_ptr<Type>(t);
} else{
foo(t);
}
/* ici, qu'est ce qu'on a ??? */
t->behaviour(); // une chance sur deux de planter
// une chance sur deux d'avoir un résultat totalement incohérent
} |
Et ca, c'est sans compter toutes les fonctions et toutes les classes qui, d'une manière ou d'une autre, vont manipuler un Type * et qui risquent d'appeler bar, même si c'est de manière indirecte.
Et ce risque est présent quel que soit le comportement de behaviour (j'aurais tout aussi bien pu écrire delete t à la place ;)).
Et si ce n'est pas dans ton propre code que le problème survient, tu peux t'attendre à ce qu'il survienne dans le code utilisateur ;).
Il dit deux choses:
Citation:
- return std::move(varlocale); est à éviter car return varlocale; est au moins aussi efficace. Personne n'a écrit le contraire. Je ne vois pas en quoi ça déconseille d'avoir unique_ptr<T> comme type de retour.
Encore une fois, tu expose un détail d'implémentation, qui aura certes un comportement identique à ce que je préconise, qui, effectivement, se propagera normalement sur l'ensemble du projet, mais qui laisse à penser qu'il n'y a "qu'une certaine catégorie de fonctions" qui présentent ce comportement (sous entendu : c'est qu'il y a "une autre catégorie de fonction" qui ne prendra pas la responsabilité du pointeur).
Au final, on en arrive, je te l'accorde, strictement au même point, à savoir que l'on arrive à avoir une approche globale du fait que les classes / fonction manipulent std::unique_ptr (mais exclusivement celui-là et pas un autre NihUnique_ptr qui ne faisait de mal à personne et qui aurait très bien pu rester à sa place, tant qu'il faisait ce qu'on attendait de lui (sans avoir la moindre nostalgie NIH ;)) ), si ce n'est:
- Que tu remplaces une règle générale ("il y a d'office quelque chose qui prend la responsabilité du pointeur, à vous de veiller à la récupérer (ou non) selon vos besoin") en un ensemble de règle particulières (à chaque fonction spécifique qui renvoit un unique_ptr)
- Que cela ne te dispense absolument pas de fournir l'un ou l'autre comportement associé à l'utilisation de unique_ptr (reset() :question: get() :question:)
- Que la règle générale est et restera donc "attention, chaque new doit être associé à un delete correspondant... sauf (la liste de toutes les fonctions)".
Au final, tu te retrouves avec un règle de base vidée de sa substance ( à cause des exceptions qui couvre l'intégralité du scope de la règle de base ) et une exception par fonction... auxquelles il faut ajouter "l'exception à l'exception" pour chaque comportement similaire à get().
Mais bons dieux! faisons simple, aussi bien pour la règle que pour l'interface!
Les fonctions renvoient, quand c'est nécessaire, des pointeurs mais sont claires sur ce qu'elles font de la responsabilité, sans exposer le std::unique_ptr et la règle devient "la responsabilité d'un pointeur est, d'office, prise en charge par quelque chose, si vous voulez qu'il vous la donne, vous le lui demandez".
Suis-je vraiment le seul à considérer que cette approche est beaucoup plus simple pour tout le monde :question: