web-dev-qa-db-fra.com

Correction de la constance du pointeur intelligent C ++

J'ai quelques conteneurs dans une classe, par exemple, un vecteur ou une carte qui contient shared_ptr's aux objets vivant sur le tas.

Par exemple

template <typename T>
class MyExample
{
public:

private:
    vector<shared_ptr<T> > vec_;
    map<shared_ptr<T>, int> map_;
};

Je veux avoir une interface publique de cette classe qui retourne parfois shared_ptrs aux objets const (via shared_ptr<const T>) et parfois shared_ptr<T> où j'autorise l'appelant à muter les objets.

Je veux une constance logique correcte, donc si je marque une méthode comme const, elle ne peut pas changer les objets sur le tas.

Des questions:

1) Je suis confus par l'interchangeabilité de shared_ptr<const T> et shared_ptr<T>. Quand quelqu'un passe un shared_ptr<const T> dans la classe, dois-je:

  • Stockez-le en tant que shared_ptr<T> ou shared_ptr<const T> à l'intérieur du conteneur?
  • OR
  • Dois-je changer la carte, les types de vecteurs (par exemple, insert_element (shared_ptr<const T> obj)?

2) Est-il préférable d'instancier les classes comme suit: MyExample<const int>? Cela semble indûment restrictif, car je ne pourrai jamais retourner un shared_ptr<int>?

42
user231536

shared_ptr<T> et shared_ptr<const T> sont pas interchangeables. Ça va dans un sens - shared_ptr<T> est convertible en shared_ptr<const T> mais pas l'inverse.

Observer:

// f.cpp

#include <memory>

int main()
{
    using namespace std;

    shared_ptr<int> pint(new int(4)); // normal shared_ptr
    shared_ptr<const int> pcint = pint; // shared_ptr<const T> from shared_ptr<T>
    shared_ptr<int> pint2 = pcint; // error! comment out to compile
}

compiler via

cl/EHsc f.cpp

Vous pouvez également surcharger une fonction basée sur une constance. Vous pouvez combiner pour faire ces deux faits pour faire ce que vous voulez.

Quant à votre deuxième question, MyExample<int> est probablement plus logique que MyExample<const int>.

37
Terry Mahaffey

Je suggérerais la méthotologie suivante:

template <typename T>
class MyExample
{
  private:
    vector<shared_ptr<T> > data;

  public:
    shared_ptr<const T> get(int idx) const
    {
      return data[idx];
    }
    shared_ptr<T> get(int idx)
    {
      return data[idx];
    }
    void add(shared_ptr<T> value)
    {
      data.Push_back(value);
    }
};

Cela garantit une constance correcte. Comme vous le voyez, la méthode add () n'utilise pas <const T> mais <T> car vous avez l'intention que la classe stocke Ts et non const Ts. Mais lorsque vous y accédez const, vous retournez <const T> ce qui n'est pas un problème car shared_ptr <T> peut facilement être converti en shared_ptr <const T>. Et les deux méthodes get () renvoient des copies des shared_ptr dans votre mémoire interne, l'appelant ne peut pas accidentellement changer l'objet vers lequel pointent vos pointeurs internes. Tout cela est comparable à la variante de pointeur non intelligent:

template <typename T>
class MyExamplePtr
{
  private:
    vector<T *> data;

  public:
    const T *get(int idx) const
    {
      return data[idx];
    }
    T *get(int idx)
    {
      return data[idx];
    }
    void add(T *value)
    {
      data.Push_back(value);
    }
};
12
mmmmmmmm

Si quelqu'un vous passe un shared_ptr<const T>, Vous ne devriez jamais pouvoir modifier T. Il est, bien sûr, techniquement possible de transtyper le const T En un simple T, mais cela brise l'intention de rendre le Tconst. Donc, si vous voulez que les gens puissent ajouter des objets à votre classe, ils devraient vous donner shared_ptr<T> Et non shared_ptr<const T>. Lorsque vous retournez des éléments de votre classe que vous ne souhaitez pas modifier, c'est lorsque vous utilisez shared_ptr<const T>.

shared_ptr<T> Peut être automatiquement converti (sans cast explicite) en shared_ptr<const T> Mais pas l'inverse. Cela peut vous aider (et vous devriez quand même le faire) à faire un usage libéral des méthodes const. Lorsque vous définissez une méthode de classe const, le compilateur ne vous permet pas de modifier vos membres de données ni de renvoyer quoi que ce soit, sauf un const T. Ainsi, l'utilisation de ces méthodes vous aidera à vous assurer que vous n'avez pas oublié quelque chose et aidera les utilisateurs de votre classe à comprendre l'intention de la méthode. (Exemple: virtual shared_ptr<const T> myGetSharedPtr(int index) const;)

Vous avez raison sur votre deuxième instruction, vous ne voulez probablement pas instancier votre classe en tant que <const T>, Car vous ne pourrez jamais modifier aucun de vos Ts.

6
SoapBox

une chose à réaliser est que:

tr1::shared_ptr<const T> imite la fonctionnalité de T const * à savoir ce qu'il pointe vers const, mais pas le pointeur lui-même.

Vous pouvez donc attribuer une nouvelle valeur à votre pointeur partagé, mais je m'attendrais à ce que vous ne puissiez pas utiliser le déréférencé shared_ptr en tant que valeur l.

3
Evan Teran

Prologue

Le qualificatif const modifie le comportement de std::shared_ptr, tout comme cela affecte les pointeurs C hérités.

Les pointeurs intelligents doivent être gérés et stockés en utilisant les bons qualificatifs à tout moment pour empêcher, appliquer et aider les programmeurs à les traiter correctement.

Réponses

  1. Quand quelqu'un passe un shared_ptr<const T> dans la classe, dois-je le stocker en tant que shared_ptr<T> ou shared_ptr<const T> à l'intérieur du vecteur et de la carte ou dois-je changer la carte, les types de vecteur?

Si votre API accepte un shared_ptr<const T>, le contrat tacite entre l'appelant et vous-même est que vous n'êtes PAS autorisé à modifier l'objet T pointé par le pointeur, vous devez donc le conserver tel quel dans vos conteneurs internes, par exemple std::vector<std::shared_ptr<const T>>.

De plus, votre module ne devrait JAMAIS pouvoir/être autorisé à retourner std::shared_ptr<T>, même si on peut y parvenir par programmation (Voir ma réponse à la deuxième question pour voir comment).

  1. Est-il préférable d'instancier les classes comme suit: MyExample<const int>? Cela semble indûment restrictif, car je ne pourrai jamais retourner un shared_ptr<int>?

Ça dépend:

  • Si vous avez conçu votre module de façon à ce que les objets qui lui sont transmis ne changent plus à l'avenir, utilisez const T comme type sous-jacent.

  • Si votre module doit être capable de renvoyer des pointeurs non const T, vous devez utiliser T comme type sous-jacent et probablement avoir deux getters différents, l'un qui renvoie des objets mutables (std::shared_ptr<T>) et un autre qui renvoie des objets non modifiables (std::shared_ptr<const T>).

Et, même si j'espère que nous venons de vous convenir ne devrait pas retourner std::shared_ptr<T> si tu as un const T ou std::shared_ptr<const T>, vous pouvez:

const T a = 10;
auto a_ptr = std::make_shared<T>(const_cast<T>(a));

auto b_const_ptr = std::make_shared<const T>();
auto b_ptr = std::const_pointer_cast<T>(b_const_ptr);

Exemple complet

Considérons l'exemple suivant qui couvre toutes les permutations possibles de const avec std::shared_ptr:

struct Obj
{
  int val = 0;
};

int main()
{
    // Type #1:
    // ------------
    // Create non-const pointer to non-const object
    std::shared_ptr<Obj> ptr1 = std::make_shared<Obj>();
    // We can change the underlying object inside the pointer
    ptr1->val = 1;
    // We can change the pointer object
    ptr1 = nullptr;

    // Type #2:
    // ------------
    // Create non-const pointer to const object
    std::shared_ptr<const Obj> ptr2 = std::make_shared<const Obj>();
    // We cannot change the underlying object inside the pointer
    ptr2->val = 3; // <-- ERROR
    // We can change the pointer object
    ptr2 = nullptr;

    // Type #3:
    // ------------
    // Create const pointer to non-const object
    const std::shared_ptr<Obj> ptr3 = std::make_shared<Obj>();
    // We can change the underlying object inside the pointer
    ptr3->val = 3;
    // We can change the pointer object
    ptr3 = nullptr; // <-- ERROR

    // Type #4:
    // ------------
    // Create const pointer to non-const object
    const std::shared_ptr<const Obj> ptr4 = std::make_shared<const Obj>();
    // We can change the underlying object inside the pointer
    ptr4->val = 4; // <-- ERROR
    // We can change the pointer object
    ptr4 = nullptr; // <-- ERROR

    // Assignments:
    // ------------
    // Conversions between objects
    // We cannot assign to ptr3 and ptr4, because they are const
    ptr1 = ptr4 // <-- ERROR, cannot convert 'const Obj' to 'Obj'
    ptr1 = ptr3;
    ptr1 = ptr2 // <-- ERROR, cannot convert 'const Obj' to 'Obj'

    ptr2 = ptr4;
    ptr2 = ptr3;
    ptr2 = ptr1;
}

Remarque: ce qui suit est vrai lors de la gestion de tous les types de pointeurs intelligents. L'affectation des pointeurs peut différer (par exemple lors de la manipulation de unique_ptr), mais le concept est le même.

0
Daniel Trugman