Citation Envoyé par gbdivers Voir le message
Si on a
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
class A {
    // fonctions pour une sémantique de collection
    vector<unique_ptr<B>> v;
    B* get(size_t);
 
    // autres fonctions
    ...
};
La classe est vue comme une collection de B + une autre sémantique. N'a-t-on pas un problème de respect du SRP ? A priori, je dirais oui et il faudrait donc partager les responsabilité. Et dans ce cas, qu'apporterait une classe CollectionDeB en plus d'un simple typedef ? (ou dit autrement, si une CollectionDeB apporte autre chose, n'est-on pas en face d'une violation de SRP ?)
Ce que tu décris là, une classe gérant un conteneur et ayant une sémantique de conteneur, c'est (la généricité en moins dans ton exemple), l'idée d'un adaptateur de conteneurs. Là on parle hors de tout contexte, donc ta conclusion est prématuré AMA, un adaptateur de conteneur peut avoir une responsabilité parfaitement définie.

Citation Envoyé par gbdivers Voir le message
Autre point, B* get(size_t); est une violation de Demeter. Si elle était respecté, vector<unique_ptr<B>> serait manipulé uniquement en interne et donc celui qui modifie A sait qu'il travaille sur unique_ptr ou sur un pointeur nu issu de unique_ptr et donc qu'il n'a pas la responsabilité. On a une colocalisation de l'information "unique_ptr a la responsabilité de la durée de vie" et l'utilisation de B*. En ne respectant pas Demeter, l'utilisateur de B* ne sait pas si cet objet est issu de unique_ptr ou si c'est un pointeur nu à l'origine. On sépare l'information sur la responsabilité de la durée de vie (indiqué par unique_ptr) et son utilisation, d'où le risque d'erreur (faire un delete sur un objet géré par unique_ptr).
Si la classe a une sémantique de conteneur, Demeter ne s'applique pas sur lui. Pour le reste, en effet on ne sait pas (hors documentation (*)), que le pointeur retourné par le conteneur n'a pas à être pris en responsabilité. Ou plus exactement (et c'est ce qui appuie les propos de Loic si j'ai bien compris), l'existance d'une évolution dans le C++ fait qu'on ne sait pas avec certitude si le code est du "C++ moderne" où dans ce cas T* est membre du couple std::unique_ptr/T* (**) et donc qu'il n'a aucune responsabilité, où si c'est du C++ "plus ancien" où il n'y a pas de vraie sémantique associé à T*.

Je trouve que la lien est incomplet. Dans le contexte actuelle, le besoin d'une conversion implicite d'un std::unique_ptr vers un no_own_ptr (représenté par T* actuellement), n'est pas un besoin sans intérêt. Bien entendue l'argumentaire reste totalement valide pour une fonction get pensée dans l'esprit retro-compatibilité, comme pour std::shared_ptr, ce qui appuie encore l'idée de Loic d'un besoin d'un capsule no_own_ptr (peu importe le nom) pour séparer les deux utilités actuellement tenues par un seul élément). Dans le cas d'une telle capsule, il n'y aurait pas besoin d'écrire un opérateur de cast, le constructeur de no_own_ptr devrait suffire (comme pour std::shared_ptr/std::weak_ptr).

(*) Et c'est pas le seul élément du C++ où le besoin de documentation est nécessaire pour savoir quoi faire.
(**) Si on occulte totalement le besoin de se marier avec du code "ancien" le besoin d'un T* pour représenter autre chose qu'un pointeur sans responsabilité est quasi-nul.