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:
shared_ptr<T>
ou shared_ptr<const T>
à l'intérieur du conteneur?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>
?
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>
.
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);
}
};
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 T
const
. 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 T
s.
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.
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.
- Quand quelqu'un passe un
shared_ptr<const T>
dans la classe, dois-je le stocker en tant queshared_ptr<T>
oushared_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).
- 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 unshared_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);
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.