2010-01-17 3 views
37

J'ai quelques conteneurs dans une classe, par exemple, vecteur ou carte qui contiennent shared_ptr aux objets vivant sur le tas.C++ pointeur intelligent const correct

Par exemple

template <typename T> 
class MyExample 
{ 
public: 

private: 
vector<tr1::shared_ptr<T> > vec; 
map<tr1::shared_ptr<T> , int> h; 
}; 

Je veux avoir une interface publique de cette classe qui retourne parfois shared_ptrs à Const objets (via shared_ptr<const T>) et parfois shared_ptr<T> où je permettre à l'appelant de muter les objets. Je veux l'exactitude logique const, donc si je marque une méthode comme const, il ne peut pas changer les objets sur le tas.

Questions:

1) Je suis confus par l'interchangeabilité des tr1::shared_ptr<const T> et tr1::shared_ptr<T>. Quand quelqu'un passe un shared_ptr<const T> shared_ptr dans la classe, ce que je stocke comme un shared_ptr<T> ou shared_ptr<const T> à l'intérieur du vecteur et carte ou changer la carte, types de vecteurs (par exemple insert_elemeent (shared_ptr<const T> obj)?

2) Est-il mieux instancier les classes comme suit: MyExample<const int>? Cela semble trop restrictif, parce que je ne peux jamais retourner un shared_ptr<int>?

Répondre

3

une chose à réaliser est que:

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

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

+1

"l-value". Une valeur l n'a pas besoin d'être assignable! – curiousguy

5

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 transformer le const T en un T, mais cela rompt l'intention de créer 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 renvoyez des éléments de votre classe, vous ne voulez pas les modifier, c'est-à-dire lorsque vous utilisez shared_ptr<const T>.

shared_ptr<T> peut être automatiquement converti (sans une distribution explicite) en shared_ptr<const T> mais pas l'inverse. Cela peut vous aider (et vous devriez le faire de toute façon) à faire un usage généreux des méthodes const. Lorsque vous définissez une méthode de classe const, le compilateur ne vous permet pas de modifier l'un de vos membres de données ou de renvoyer quoi que ce soit à l'exception d'un const T. 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 déclaration, vous ne voulez probablement pas à instancier votre classe comme <const T>, puisque vous ne serez jamais en mesure de modifier l'un de vos T s.

34

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

Observe:

// 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 
} 

compilez via

cl/EHsc f.cpp

Vous pouvez également surcharger une fonction basée sur un constness. Vous pouvez combiner ces deux faits pour faire ce que vous voulez. En ce qui concerne votre deuxième question, MyExample<int> a probablement plus de sens que MyExample<const int>.

11

je suggère la methotology 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 const-exactitude. Comme vous le voyez, la méthode add() n'utilise pas < const T> mais < T> parce que vous avez l'intention que la classe stocke Ts non const Ts. Mais en y accédant const, vous retournez < const T> ce qui n'est pas un problème puisque shared_ptr T> peut facilement être converti en shared_ptr < const T>. Et si les deux méthodes get() renvoient des copies des shared_ptr dans votre stockage interne, l'appelant ne peut pas modifier accidentellement l'objet pointé par 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); 
    } 
}; 
+0

Ne devrait-il pas être que 'shared_ptr ' peut facilement être converti en shared_ptr 'et non l'inverse? – user231536

+0

Lors du retour d'un simple membre, la surcharge ne semble pas être un gros problème. Mais que faire si vous renvoyez un 'shared_ptr' d'un' vector' - où vous devez calculer lequel est le bon. Comment évitez-vous la duplication de code? – thomthom