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 :

Pourquoi std::unique_ptr ne propage-t-il pas const ?


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 513
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 513
    Par défaut Pourquoi std::unique_ptr ne propage-t-il pas const ?
    Bonjour,

    Actuellement, dans std::unique_ptr<T, Deleter>, on a des fonctions constantes qui permettent de modifier la donnée pointée de type T :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    pointer get() const;
    pointer operator->() const;
    typename std::add_lvalue_reference<T>::type operator*() const;
    avec pointer qui correspond à std::remove_reference<Deleter>::type::pointer si ce type existe, T* sinon.

    Pourquoi std::unique_ptr a-t-il été conçu ainsi et pas comme ci-dessous ?
    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
    template<class T, class Deleter = std::default_delete<T>>
    class unique_ptr
    {
    private:
        typedef typename std::add_const<T>::type const_T;
        // ...
    public:
        typedef T&       reference;
        typedef const_T& const_reference;
        typedef T*       pointer;
        typedef const_T* const_pointer;
        pointer         get();
        const_pointer   get() const;
        pointer         operator->();
        const_pointer   operator->() const;
        reference       operator*();
        const_reference operator*() const;
        // ...
    };
    Une partie de la réponse est que std::unique_ptr a été conçu pour remplacer entièrement std::auto_ptr, mais la même question s'applique à ce dernier.
    Pourquoi std::auto_ptr<T> contient-il ces fonctions :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    T* get()        const;
    T* operator->() const;
    T& operator*()  const;
    au lieu des fonctions ci-dessous ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    T*       get();
    const T* get()        const;
    T*       operator->();
    const T* operator->() const;
    T&       operator*();
    const T& operator*()  const;
    Actuellement, pour propager const, on peut utiliser std::experimental::propagate_const.
    Par exemple, sur la page de en.cppreference.com consacrée à PImpl, le pointeur intelligent utilisé est un std::experimental::propagate_const<std::unique_ptr<impl>>.

    Mais à quoi ça sert de pouvoir ne pas propager const avec un std::unique_ptr ?

    Pour l'instant, le seul cas où ça m'a servi, c'est quand j'ai utilisé un std::unique_ptr<ClasseMalCodee>ClasseMalCodee était une classe d'une bibliothèque externe où il manquait des const.

  2. #2
    Membre Expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    760
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2011
    Messages : 760
    Par défaut
    Parce qu'ajouter const à un pointeur ne rend pas la valeur constante ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    using P = int *;
    P const p = ...;
    *p = 2; // ok
    C'est discutable, mais l'inverse empêche de remplacer des pointeurs par des pointeurs intelligents dans toutes les situations.

  3. #3
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 513
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 513
    Par défaut
    Quand on a un pointeur qui pointe vers une donnée qu'il ne possède pas, c'est normal que const ne soit pas automatiquement propagé.

    Mais, un std::unique_ptr<T>, en principe, c'est un peu comme un std::optional<T> pour lequel la donnée est stockée ailleurs dans la mémoire dynamique au lieu d'être stockée sur place, avec les autres changements que cela implique.
    Mais je ne vois pas pourquoi std::unique_ptr ne propagerait pas const alors que std::optional le fait bien.

    Ce qui renforce mon étonnement, c'est que d'autres bibliothèques qui ont l'équivalent de std::unique_ptr ne propagent pas const non plus, par exemple boost::scoped_ptr dans Boost et QScopedPointer dans Qt.

    Pour illustrer l'étrangeté de la non propagation de const avec std::unique_ptr, admettons que j'ai un objet sacDeFruits qui contient un std::vector<std::unique_ptr<Fruit>> dont le premier élément contient un pointeur vers un objet de type Pomme et le deuxième un pointeur vers un objet de type Poire.
    Si mon objet sacDeFruits est const, je ne peux changer ni le nombre de fruits, ni leurs types, mais je peux quand même modifier les fruits (par exemple leurs dates de péremption).

  4. #4
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 397
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 397
    Par défaut
    Le problème majeur, c'est qu'on ne peut pas inconditionnellement propager const, parce qu'il y a toujours le potentiel pour du code qui a besoin d'un pointeur intelligent qui ne le propage pas!
    Il faut donc que la propagation du const, s'il y a, reste optionnelle, ce qui complique le template et surtout les copies déplacements entre instances: Faut-il interdire le déplacement depuis un pointeur propageant vers un pointeur non-propageant? etc.

    Edit: J'ai trouvé une raison beaucoup plus simple, liée justement à la possession d'objet par un autre: Un objet peut être fait pour garder toute sa vie un pointeur vers un autre, tout en ayant besoin de le modifier:

    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    class MonObjet
    {
    	const unique_ptr<Truc> pTruc;
    public:
    	MonObjet(unique_ptr<Truc> && pTruc) : pTruc(move(pTruc)) { if(pTruc==nullptr) throw invalid_argument("pTruc"); }
     
    	void FaireQuelqueChose() { pTruc ->MethodeVirtuelleNonConst(); }
    };
    Ici, on ne veut clairement pas que unique_ptr propage const, et on n'a pas de raison de le mettre non-const (mais s'il propageait const, on aurait une raison).
    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.

  5. #5
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 513
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 513
    Par défaut
    Citation Envoyé par Médinoc Voir le message
    Edit: J'ai trouvé une raison beaucoup plus simple, liée justement à la possession d'objet par un autre: Un objet peut être fait pour garder toute sa vie un pointeur vers un autre, tout en ayant besoin de le modifier:

    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    class MonObjet
    {
    	const unique_ptr<Truc> pTruc;
    public:
    	MonObjet(unique_ptr<Truc> && pTruc) : pTruc(move(pTruc)) { if(pTruc==nullptr) throw invalid_argument("pTruc"); }
     
    	void FaireQuelqueChose() { pTruc ->MethodeVirtuelleNonConst(); }
    };
    Pour ce besoin-là, j'aurais créé un nouveau modèle de classe, memory_guard, qui serait un peu comme std::unique_ptr, mais sans les opérations permettant de pointer ailleurs (reset, release, swap, constructeur de mouvement et affectation de mouvement).

    On aurait la même dualité que pour std::lock_guard et std::unique_lock : un modèle de classe RAII pure dont la ressource est acquise à la construction et n'est libérée qu'à la destruction et un autre modèle de classe un peu plus souple.

    Alors, memory_guard et unique_ptr pourraient propager const tous les deux.

    Dans ton exemple, MonObjet aurait un memory_guard.
    On aurait à la fois l'avantage que tu souhaites (pointer toujours vers le même objet Truc) et l'avantage d'avoir une const-correctness plus cohérente : quand un objet est const, les sous-objets qu'il possède sont const aussi.

  6. #6
    Membre Expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    760
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2011
    Messages : 760
    Par défaut
    Citation Envoyé par Pyramidev Voir le message
    Quand on a un pointeur qui pointe vers une donnée qu'il ne possède pas, c'est normal que const ne soit pas automatiquement propagé.
    Et du coup, quelle différence il y a entre un pointeur qui possède la donnée et un pointeur qui ne la possède pas ? Pourquoi ne pas posséder la valeur empêcherait la propagation du const ?

    Le problème est que le pointeur à 2 niveaux totalement dissocié où mettre const: le pointeur et le pointé. La stratégie choisie est que le pointeur ne s'occupe que de lui même, pas du pointé. Chacun a ses qualificateurs qui ne sont pas propagés de l'un l'autre.

    D'ailleurs, le même problème se pose avec volatile et là, on ne veut clairement pas le propager.

    Avec un pointeur intelligent, on s'attend naturellement au même comportement: ce n'est pas parce que je ne veux pas modifier la valeur du pointeur que je ne veux pas modifier la valeur du pointé. C'est le cas, std::unique_ptr et consort ne font qu'utiliser le pointeur, jamais la valeur directement. Si on wrap un type T alors on n'accède à un type T en toute circonstance. Si on wrap un type T const, alors on accède à un type T const.

    std::propagate_const répond à un autre problème lié aux templates: Ptr<T> n'est pas convertible en Ptr<T const> sans créer un nouvel objet et dupliquer les pointeurs avec des effets de bord non désirables.

    Citation Envoyé par Pyramidev Voir le message
    Mais, un std::unique_ptr<T>, en principe, c'est un peu comme un std::optional<T> pour lequel la donnée est stockée ailleurs dans la mémoire dynamique au lieu d'être stockée sur place, avec les autres changements que cela implique.
    Mais je ne vois pas pourquoi std::unique_ptr ne propagerait pas const alors que std::optional le fait bien.
    Parce que dans "les autres changements que cela implique" il y a "n'est pas un pointeur". Et surtout parce que toute modification de std::optional modifie forcement la valeur.

  7. #7
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 513
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 513
    Par défaut
    Citation Envoyé par jo_link_noir Voir le message
    Et du coup, quelle différence il y a entre un pointeur qui possède la donnée et un pointeur qui ne la possède pas ? Pourquoi ne pas posséder la valeur empêcherait la propagation du const ?
    Ne pas posséder la valeur n'empêche pas forcément la propagation du const. Mais, à mon avis, posséder la valeur oblige à propager const.
    Si une classe A possède un pointeur vers une classe B sans posséder B, modifier B ne modifiera pas A. Ainsi, il n'est pas nécessaire de propager const.
    Par contre, si une classe A possède une classe B, modifier B modifiera A. Ainsi, il est nécessaire de propager const, peu importe que l'objet contenu soit derrière un pointeur ou non.

    Dans la suite de mon message, je distinguerai :
    • std::unique_ptr<T, Deleter> qui fonctionne tel qu'il est actuellement, donc qui ne propage pas const.
    • unique_memory<T, Deleter> qui propage const. Ainsi, const unique_memory<T, Deleter> est analogue à const std::unique_ptr<const T, Deleter>.
    • memory_guard<T, Deleter> qui est pareil que unique_memory<T, Deleter>, mais sans les opérations permettant de pointer ailleurs (reset, release, swap, constructeur de mouvement et affectation de mouvement). Ainsi, memory_guard<T, Deleter> est analogue à const std::unique_ptr<T, Deleter> et const memory_guard<T, Deleter> est analogue à const std::unique_ptr<const T, Deleter>.


    Quelqu'un aurait-il un exemple concret où il serait plus pertinent qu'une classe possède un objet membre de type std::unique_ptr plutôt qu'un objet membre de type unique_memory ou memory_guard ?

  8. #8
    Membre Expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    760
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2011
    Messages : 760
    Par défaut
    Citation Envoyé par Pyramidev Voir le message
    Quelqu'un aurait-il un exemple concret où il serait plus pertinent qu'une classe possède un objet membre de type std::unique_ptr plutôt qu'un objet membre de type unique_memory ou memory_guard ?
    Je pense que unique_memory et memory_guard répondent à 99% des besoins. Ce serait en fait l'équivalent de std::propagate_const<std::unique_ptr<T>> et std::propagate_const<const std::unique_ptr<T>>.

    Le seul cas du 1% qui me vient est par l'intermédiaire de conteneur où memory_guard n'est pas applicable (un std::vector<std::unique_ptr<T>> construit en interne d'une classe est accessible à l'extérieur sans autorisation de modifier le vecteur ou les pointeurs, mais sans restriction sur les valeurs pointées, la classe ne s'occupe que de construire une liste cohérente).
    Chose résolue en partie par un intermédiaire qui s'occupe de cacher le unique_ptr (en retournant toujours un pointeur ou une référence) et en appliquant const au besoin. Une espèce de propagate_const pour les conteneurs.

Discussions similaires

  1. Réponses: 1
    Dernier message: 18/05/2006, 14h09
  2. [Etudes] Pourquoi l'e-learning ne s'impose pas définitivement?
    Par kisitomomotene dans le forum Etudes
    Réponses: 4
    Dernier message: 12/05/2006, 18h02
  3. Réponses: 11
    Dernier message: 11/05/2006, 09h05
  4. Réponses: 11
    Dernier message: 04/05/2006, 11h50
  5. Réponses: 3
    Dernier message: 10/05/2005, 14h43

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