2
votes

Comment retourner un pointeur privé vers une liste de pointeurs en tant que const?

J'ai un pointeur vers une liste de pointeurs, en tant que variable privée. J'ai aussi un getter qui renvoie le pointeur vers la liste. J'ai besoin de le protéger des changements.

Je n'ai pas trouvé comment utiliser reinterpret_cast ou const_cast à ce sujet.

shared_ptr<list<shared_ptr<const typeB>>> 

Le compilateur renvoie:

const shared_ptr<const list<shared_ptr<const typeB>>>

Il semble que const shared_ptr > > et shared_ptr >> fonctionnent bien.

Est-il possible de faire return l comme un const complet, comme:

   error: could not convert ‘((typeA*)this)->typeA::x’ from ‘std::shared_ptr<std::__cxx11::list<std::shared_ptr<typeB> > >’ to ‘std::shared_ptr<std::__cxx11::list<std::shared_ptr<const typeB> > >’|
||=== Build failed: 1 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|

ou au moins comme: p>

class typeA{
    shared_ptr<list<shared_ptr<typeB>>> l;
public:
   shared_ptr<list<shared_ptr<const typeB>>> getList(){return (l);};
};

?

Les références au lieu de pointeurs ne sont pas une option. Déclarer l comme shared_ptr >> n'est pas non plus une solution souhaitée.

EDIT: plus de 'int'.

Il semble que ce n'est pas possible exactement ce que je voulais, mais les solutions suggérées sont bonnes. Oui, la copie de pointeurs est acceptable.

Mon mauvais je n'ai pas mis typeB immédiatement. Je connais certains avantages des références par rapport aux pointeurs, mais j'espérais qu'il existe une solution similaire.


12 commentaires

Je suis plus curieux de savoir pourquoi vous avez un pointeur vers un conteneur? Quel est le cas d'utilisation pour cela? Et pourquoi avez-vous un conteneur de pointeurs vers int ? Quel est le cas d'utilisation pour cela?


Ce n'est pas Java. Votre code semble suspect.


que diriez-vous simplement de list ? Pourquoi n'est-ce pas une solution recherchée?


@FantasticMrFox À moins d'exigences particulières, std::vector est préférable IMO.


Dans mon code, j'ai beaucoup de procédures qui créent un objet et le renvoient. La seule façon de faire est de retourner par valeur ou de renvoyer un pointeur. Le premier est cher. Dans ce cas, je n'ai pas besoin d'un pointeur vers le conteneur, mais je ne veux pas mélanger les références et les pointeurs. Il est plus facile et plus propre de n'avoir que des pointeurs.


Je pense que vous devrez peut-être obtenir quelques bons livres et lisez les références . Et avoir des pointeurs vers des valeurs uniques int est inutile (sans jeu de mots).


J'ai mis «int» pour être plus facile à lire, mais je peux changer.


J'avais peur des commentaires comme celui-ci. Le point n'est pas int. Le point est ce que j'ai demandé.


Cela semble être un x y problème . Si vous posez des questions sur votre problème d'origine, vous pouvez trouver une meilleure solution que d'essayer de faire fonctionner cette solution complexe.


@ HD2000 Je pensais déjà que vous pourriez avoir l'intention d'utiliser int comme espace réservé pour un objet plus grand ... Mais si vous expliquez explicitement avant de présenter le code, les gens le savent ... Par contre, si vous avez écrit shared_ptr >> , la question aurait également été claire. Bien que mieux si vous le faisiez, j'accepterais même de ne pas pré-déclarer TypeB dans ce cas très spécifique car ce que vous avez l'intention de demander est toujours clair ...


@Fox: C'est peut-être un problème XY, mais néanmoins ça ne fait pas de mal de répondre, s'il y a une solution, ou de dire qu'il n'y a pas de solution.


@ HD2000 Le point sur les problèmes XY est: Nous pourrions vous présenter quelque chose qui résout correctement le problème que vous avez présenté, juste pour vous faire découvrir par la suite que tout ce que vous essayez de faire ne fonctionne toujours pas ... Ou vous obtenez une solution beaucoup plus complexe que nécessaire ; imaginez que vous nous présentez: 'Je veux résumer tous les éléments dans un vecteur' - et invisible pour nous, vous remplissez le vecteur avec 1, 2, 3, 4, 5, ..., n . Si vous avez demandé à la place «Je veux résumer les n premiers nombres naturels» , vous auriez obtenu la formule bien connue ...


5 Réponses :


2
votes

Lorsque vous convertissez un pointeur vers non constant en pointeur vers const, vous avez deux pointeurs. De plus, une liste de pointeurs vers non constants est un type complètement différent d'une liste de pointeurs vers const.

Ainsi, si vous voulez renvoyer un pointeur vers une liste de pointeurs vers const, ce que vous devez have est une liste de pointeurs vers const. Mais vous n'avez pas une telle liste. Vous avez une liste de pointeurs vers non constants et ces types de listes ne sont pas interconvertibles.

Bien sûr, vous pouvez transformer vos pointeurs vers non constants en une liste de pointeurs vers const, mais vous devez comprenez qu'il s'agit d'une liste distincte. Un pointeur vers l'ancien type ne peut pas pointer vers le dernier.

Voici donc un exemple pour transformer la liste (je n'ai pas testé, peut contenir des fautes de frappe ou des erreurs):

 list<shared_ptr<const int>> getConstCopyList() {
      // ... the transorm above
      return const_copy_of_list;
 }

Puisque nous avons créé une liste complètement nouvelle, qui ne peut pas être pointée par l , il est peu logique de renvoyer un pointeur. Retournons la liste elle-même. L'appelant peut envelopper la liste en propriété partagée s'il en a besoin, mais ce n'est pas obligatoire lorsque cela va à l'encontre de ses besoins:

list<shared_ptr<const int>> const_copy_of_list;
std::transform(l->begin(), l->end(), std::back_inserter(const_copy_of_list),
               [](auto& ptr) {
    return static_pointer_cast<const int>(ptr);
});
// or more simply as shown by Ted:
list<shared_ptr<const int>> const_copy_of_list(l->begin(), l->end());

Notez que bien que la liste soit séparée, les pointeurs à l'intérieur pointent toujours vers les mêmes entiers.


En guise de remarque, veuillez vous demander si la propriété partagée d'un objet int a du sens pour votre programme - je suis en supposant que c'est une simplification de l'exemple.

Reconsidérer également si "Des références au lieu de pointeurs ne sont pas une option" est une exigence sensée.


2 commentaires

De tout, cela semble être la solution la plus proche. Merci.


@ HD2000 Caleth's est meilleur d'un point de vue pratique (vous pouvez continuer à utiliser list si vous en avez besoin, mais considérez si vous avez réellement besoin d'une liste ). Le mien suit simplement l'exigence arbitraire «sans références». Et l'utilisation du constructeur par Ted est plus simple que ma transformation, vous pouvez l'utiliser sans renvoyer un pointeur partagé.



2
votes

Vous pouvez créer une nouvelle liste de const int à partir de votre liste d'origine et renvoyer cela:

#include <iostream>
#include <list>
#include <memory>

struct example {
    int data;
    example(int x) : data(x) {}
};

template <class T>
class typeA {
    std::shared_ptr<std::list<std::shared_ptr<T>>> l = std::make_shared<std::list<std::shared_ptr<T>>>();
public:
    template< class... Args >
    void add( Args&&... args ) {
        l->emplace_back(std::make_shared<T>(std::forward<Args>(args)...));
    }

    // a very basic iterator that can be extended as needed   
    struct const_iterator {
        using uiterator = typename std::list<std::shared_ptr<T>>::const_iterator;
        uiterator lit;
        const_iterator(uiterator init) : lit(init) {}
        const_iterator& operator++() { ++lit; return *this; }
        const T& operator*() const { return *(*lit).get(); }
        bool operator!=(const const_iterator& rhs) const { return lit != rhs.lit; }
    };

    const_iterator cbegin() const noexcept { return const_iterator(l->cbegin()); }
    const_iterator cend() const noexcept { return const_iterator(l->cend()); }
    auto begin() const noexcept { return cbegin(); }
    auto end() const noexcept { return cend(); }
};

int main() {
    typeA<example> apa;
    apa.add(10);
    apa.add(20);
    apa.add(30);
    for(auto& a : apa) {
        // a.data = 5; // error: assignment of member ‘example::data’ in read-only object
        std::cout << a.data << "\n";
    }
}

Si vous voulez empêcher les gens d'apporter des modifications à la liste retournée, rendez-la aussi constante:

std::shared_ptr<const std::list<std::shared_ptr<const T>>> getList(){
    return std::make_shared<const std::list<std::shared_ptr<const T>>>(l->cbegin(), l->cend());
}

Le pointeur partagé retourné par cette fonction ne pointe pas vers la liste d'origine mais vers la liste nouvellement créée.

Une alternative peut être de fournir des itérateurs qui, lorsqu'ils sont déréférencés, retournent const T & (où T est le type que vous stockez réellement). De cette façon, il ne sera pas nécessaire de copier la liste entière chaque fois que vous voudrez la parcourir. Exemple:

std::shared_ptr<std::list<std::shared_ptr<const int>>> getList(){
    return std::make_shared<std::list<std::shared_ptr<const int>>>(l->begin(), l->end());
}


7 commentaires

il ne partagera pas le compteur de référence avec le shared_ptr d'origine


Votre utilisation du constructeur de liste est plus concise que ma transformation +1. Mais il peut être utile de préciser que le pointeur partagé que vous renvoyez ne pointe pas vers la même liste que le fait l .


Très agréable. Je vous remercie.


Je pense que le mieux serait de rendre la liste elle-même const aussi - car traiter deux listes différentes, ne pas autoriser la modification de la copie empêchera l'utilisateur d'insérer de nouveaux éléments tout en supposant avoir modifié l'original ...


@ HD2000 J'ai ajouté une alternative qui ne nécessite pas du tout de copier la liste.


@Aconcagua True! Ajout de cette option aussi.


@Aconcagua Je viens de remarquer votre réponse et je trouve cela bien meilleur que le mien. Je laisserai ce commentaire aux futurs archéologues. :-)



2
votes

Votre problème réside carrément à

mais je ne veux pas mélanger les références et les pointeurs. Il est plus facile et plus propre de n'avoir que des pointeurs.

Ce que vous trouvez ici, c'est que la déclaration est fausse . Une list peut lier une référence const list & , et aucun des membres de la liste ne permettra de modifier le TypeB objets.

std::shared_ptr<const typeB> add_const(std::shared_ptr<typeB> ptr)
{
    return { ptr, ptr.get() };
}

class typeA {
    std::vector<std::shared_ptr<typeB>> l;
public:
    auto getList() const { return l | std::ranges::transform(add_const); };
};

Si vous vraiment devez avoir const typeB , vous pouvez à la place renvoyer une projection de l qui a ajouté const , mais ce ne serait pas un Container , mais à la place un Range (en utilisant la bibliothèque ranges votée en C ++ 20, voir aussi son implémentation autonome )

class typeA {
    std::vector<typeB> l;
public:
    const std::vector<typeB> & getList() const { return l; };
};

Une autre alternative est que vous pouvez envelopper votre std :: shared_ptr s dans quelque chose comme std :: experimental :: propagate_const , et les renvoyer directement.


4 commentaires

Si vous appliquez ceci à une liste ou à un vecteur de pointeurs, la constance ne s'appliquera qu'à ceux-ci, mais pas aux objets pointés. La question est: pourquoi devrait-il y avoir une liste de pointeurs? Eh bien, cela pourrait être un scénario polymorphe où TypeB n'est que la classe de base ...


Il est facile de combiner des pointeurs bruts et des références, alors qu'il n'est pas possible de le faire avec des pointeurs intelligents. Donc, si j'ai un tas de procédures traitant des pointeurs, l'insertion de références est une autre difficulté, du moins pour moi.


@ HD2000 Je ne suis pas. Vous devez créer de nouveaux objets pointeurs pour les pointeurs bruts et intelligents lors du basculement entre ptr et ptr . Ce que vous devriez plutôt préférer, c'est traiter autant que possible les références (et les wrappers de référence)


Non, je dis simplement pourquoi je n'utilise pas de références - il est plus facile d'utiliser des pointeurs tout le temps, puis de penser si un objet est un pointeur ou non, et comment les transformer l'un en un autre.



1
votes

Le problème avec les modèles est que pour tout

std::shared_ptr<int const> TypeA::iterator::operator*()
{
    return std::shared_ptr<int const>(*i);
}

std::shared_ptr<int const> TypeA::iterator::operator->()
{
    return *this;
}

, deux paires C et C sont totalement classes non liées - c'est même le cas si TypeA et TypeB ne diffèrent que par const-ness.

Alors qu'est-ce que vous vouloir réellement avoir n'est techniquement pas possible. Je ne présenterai pas de nouvelle solution de contournement pour le moment, car il existe déjà, mais essayez de regarder un peu plus loin: comme indiqué dans les commentaires déjà, vous pourriez faire face à un XY problème.

La question est: que ferait un utilisateur avec une telle liste? Elle / il peut être en train de répéter dessus - ou d'accéder à des éléments uniques. Alors pourquoi ne pas faire en sorte que toute votre classe ressemble / se comporte comme une liste?

class TypeA
{
public:
    class iterator
    {
        std::list<std::shared_ptr<int>>::iterator i;         
        public:
        // implementation as needed: operators, type traits, etc.
    };
};

Si vous utilisiez un vecteur au lieu d'une liste, je fournirais encore un opérateur d'index: p>

shared_ptr<typeB /* const or not? */> operator[](size_t index);

Maintenant, un problème reste non résolu jusqu'à présent: les deux const_iterators renvoyés ont un pointeur partagé immuable, mais la pointee est toujours modifiable!

C'est un peu problématique - vous devrez maintenant implémenter votre propre classe d'itérateur:

class typeA
{
    // wondering pretty much why you need a shared pointer here at all!
    // (instead of directly aggregating the list)
    shared_ptr<list<shared_ptr<typeB>>> l;
public:
    shared_ptr<list<shared_ptr<typeB>>>::const_iterator begin() { return l->begin(); }
    shared_ptr<list<shared_ptr<typeB>>>::const_iterator end() { return l->end(); }
};

Jetez un œil à std :: iterator pour un exemple complet - sachez cependant que std :: iterator est obsolète, vous devrez donc implémenter vous-même les caractères de type.

La balise d'itérateur à utiliser serait std :: bidirectional_iterator_tag ou random_access_iterator_tag ( contiguous_iterator_tag avec C ++ 20), si vous utilisez un std :: vector à l'intérieur.

Maintenant l'important est de savoir comment mettre en œuvre deux des besoins Opérateurs ed:

template <typename T>
class C { };

Les autres opérateurs ne feraient que transmettre l'opération aux itérateurs internes (incrémenter, décrémenter si disponible, comparaison, etc.).

I ne prétendez pas que c'est le Saint Graal, le chemin que vous devez suivre en toutes circonstances. Mais c'est une alternative intéressante à considérer au moins ...


0 commentaires

2
votes

Ce que vous avez ici est une construction TRÈS complexe:

shared_ptr<list<shared_ptr<const typeB>>>

Il s'agit de trois niveaux d'indirection, dont deux ont une gestion de la durée de vie de comptage de références, et le troisième est un conteneur (et non de la mémoire -contigu à cela).

Naturellement, étant donné cette structure complexe, il ne sera pas facile de le convertir en un autre type:

shared_ptr<list<shared_ptr<typeB>>> l;

Remarquez que std: : list et std :: list sont deux types distincts par conception de bibliothèque standard. Lorsque vous souhaitez transmettre des poignées non modificatrices à vos conteneurs, vous êtes généralement censé utiliser des const_iterator .
Dans votre cas, il y a un shared_ptr en haut de la liste , vous ne pouvez donc pas utiliser d'itérateurs si vous voulez ce comportement de comptage de références.

À ce stade vient la question: voulez-vous VRAIMENT ce comportement?

  • Vous attendez-vous à une situation où votre instance typeA est détruite, mais vous avez encore d'autres instances typeA avec le même conteneur?
  • Vous attendez-vous à une situation où toutes vos instances typeA partageant le conteneur sont détruites, mais vous avez toujours des références à ce conteneur à d'autres endroits de votre environnement d'exécution?
  • Vous attendez-vous à une situation où le conteneur lui-même est détruit, mais vous avez encore des références à certains des éléments?
  • Avez-vous une raison quelconque d'utiliser std :: list au lieu de conteneurs plus conventionnels pour stocker des pointeurs partagés?

Si vous répondez OUI à toutes les puces, alors pour atteindre votre objectif, vous devrez probablement concevoir une nouvelle classe qui se comporterait comme un support pour votre shared_ptr >> , tout en fournissant uniquement un accès const aux éléments.

Si, cependant, sur l'une des puces votre réponse est NON, pensez à reconcevoir le type l . Je suggère de commencer par std::vector puis d'ajouter uniquement les modifications nécessaires une par une.


1 commentaires

Le problème avec l'itérateur de const est plutôt qu'il rend le shared_ptr const, mais pas le pointe ... L'itérateur lui-même ferait bien l'affaire: comme avec tous les itérateurs, ceux-ci peuvent être invalidés dans certaines circonstances. Vous n'avez pas besoin de l'itérateur lui-même pour participer au comptage des références - tant qu'il est valide, le pointeur partagé existe encore, donc la référence est encore comptée.