shared_ptr à un tableau: faut-il l'utiliser?

172

Juste une petite question concernant shared_ptr.

Est-ce une bonne pratique d'utiliser le shared_ptrpointage vers un tableau? Par exemple,

shared_ptr<int> sp(new int[10]);

Sinon, pourquoi pas? L'une des raisons dont je suis déjà conscient est que l'on ne peut pas augmenter / décrémenter le shared_ptr. Par conséquent, il ne peut pas être utilisé comme un pointeur normal vers un tableau.

tshah06
la source
2
FWIT, vous pouvez également envisager d'utiliser simplement std::vector. Vous devrez faire attention à passer le tableau en utilisant des références afin de ne pas en faire des copies. La syntaxe pour accéder aux données est plus propre que shared_ptr, et son redimensionnement est très très simple. Et vous obtenez tous les bienfaits de la STL si vous le souhaitez.
Nicu Stiurca
6
Si la taille du tableau est déterminée au moment de la compilation, vous pouvez également envisager d'utiliser std::array. C'est presque la même chose qu'un tableau brut, mais avec une sémantique appropriée pour une utilisation dans la plupart des composants de la bibliothèque. Surtout les objets de ce type sont détruits avec delete, non delete[]. Et contrairement à vector, il stocke les données directement dans l'objet, de sorte que vous n'obtenez aucune allocation supplémentaire.
celtschk

Réponses:

268

Avec C ++ 17 , shared_ptrpeut être utilisé pour gérer un tableau alloué dynamiquement. L' shared_ptrargument de modèle dans ce cas doit être T[N]ou T[]. Alors tu peux écrire

shared_ptr<int[]> sp(new int[10]);

À partir de n4659, [util.smartptr.shared.const]

  template<class Y> explicit shared_ptr(Y* p);

Requiert: Y doit être un type complet. L'expression delete[] p, quand Test un type de tableau, ou delete p, quand Tn'est pas un type de tableau, doit avoir un comportement bien défini et ne doit pas lever d'exceptions.
...
Remarques: Quand Test un type tableau, ce constructeur ne doit pas participer à la résolution de surcharge à moins que l'expression delete[] pest bien formée et soit Test U[N]et Y(*)[N]est convertible T*, ou Test U[]et Y(*)[]est convertible T*. ...

Pour prendre en charge cela, le type de membre element_typeest désormais défini comme

using element_type = remove_extent_t<T>;

Les éléments du tableau peuvent être accédés en utilisant operator[]

  element_type& operator[](ptrdiff_t i) const;

Nécessite: get() != 0 && i >= 0 . Si Test U[N], i < N. ...
Remarques: Quand Tn'est pas un type de tableau, il n'est pas spécifié si cette fonction membre est déclarée. Si elle est déclarée, son type de retour n'est pas spécifié, sauf que la déclaration (mais pas nécessairement la définition) de la fonction doit être bien formée.


Avant C ++ 17 , shared_ptrne pouvait pas être utilisé pour gérer des tableaux alloués dynamiquement. Par défaut, shared_ptrappellera deletel'objet géré lorsqu'il ne lui restera plus aucune référence. Cependant, lorsque vous attribuez une utilisation, new[]vous devez appeler delete[]et non deletelibérer la ressource.

Afin de l'utiliser correctement shared_ptravec un tableau, vous devez fournir un suppresseur personnalisé.

template< typename T >
struct array_deleter
{
  void operator ()( T const * p)
  { 
    delete[] p; 
  }
};

Créez le shared_ptr comme suit:

std::shared_ptr<int> sp(new int[10], array_deleter<int>());

Maintenant shared_ptrsera correctement appelé delete[]lors de la destruction de l'objet géré.

Le suppresseur personnalisé ci-dessus peut être remplacé par

  • la std::default_deletespécialisation partielle des types de tableaux

    std::shared_ptr<int> sp(new int[10], std::default_delete<int[]>());
  • une expression lambda

    std::shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; });

De plus, à moins que vous n'ayez réellement besoin d'un partage de l'objet géré, a unique_ptrest mieux adapté pour cette tâche, car il a une spécialisation partielle pour les types de tableaux.

std::unique_ptr<int[]> up(new int[10]); // this will correctly call delete[]

Modifications introduites par les extensions C ++ pour les principes de base de la bibliothèque

Une autre alternative pré-C ++ 17 à celles répertoriées ci-dessus a été fournie par la spécification technique de Library Fundamentals , qui a été augmentée shared_ptrpour lui permettre de fonctionner immédiatement dans les cas où elle possède un tableau d'objets. Le projet actuel des shared_ptrmodifications prévues pour ce TS se trouve dans N4082 . Ces changements seront accessibles via l' std::experimentalespace de noms, et inclus dans l'en- <experimental/memory>tête. Quelques-uns des changements pertinents pour la prise en charge shared_ptrdes tableaux sont:

- La définition du type de membre element_typechange

typedef T element_type;

 typedef typename remove_extent<T>::type element_type;

- Un membre operator[]est ajouté

 element_type& operator[](ptrdiff_t i) const noexcept;

- Contrairement à la unique_ptrspécialisation partielle pour les tableaux, les deux shared_ptr<T[]>et shared_ptr<T[N]>seront valides et les deux aboutiront à delete[]être appelés sur le tableau d'objets géré.

 template<class Y> explicit shared_ptr(Y* p);

Requiert : Ydoit être un type complet. L'expression delete[] p, quand Test un type de tableau, ou delete p, quand Tn'est pas un type de tableau, doit être bien formée, doit avoir un comportement bien défini et ne doit pas lever d'exceptions. Quand Test U[N], Y(*)[N]doit être convertible en T*; quand Test U[], Y(*)[]doit être convertible en T*; sinon, Y*sera convertible en T*.

Prétorien
la source
9
+1, remarque: il y a aussi des Boost shared-array.
jogojapan
5
@ tshah06 shared_ptr::getrenvoie un pointeur vers l'objet géré. Vous pouvez donc l'utiliser commesp.get()[0] = 1; ... sp.get()[9] = 10;
Praetorian
55
ALT: std::shared_ptr<int> sp( new int[10], std::default_delete<int[]>() );voir aussi en.cppreference.com/w/cpp/memory/default_delete
yohjp
2
@Jeremy Si la taille est connue au moment de la compilation, il n'est pas nécessaire d'écrire une classe pour cela, cela std::shared_ptr<std::array<int,N>>devrait suffire.
Prétorien
13
Pourquoi unique_ptrobtient-on cette spécialisation partielle mais shared_ptrpas?
Adam
28

Une alternative peut-être plus simple que vous pourriez utiliser est shared_ptr<vector<int>>.

Timmmm
la source
5
Oui, ça l'est. Ou un vecteur est un sur-ensemble d'un tableau - il a la même représentation en mémoire (plus les métadonnées) mais est redimensionnable. Il n'y a pas vraiment de situations où vous voulez un tableau mais ne pouvez pas utiliser de vecteur.
Timmmm
2
La différence, ici, est que la taille du vecteur n'est plus statique, et l'accès aux données se fera avec une double indirection. Si les performances ne sont pas le problème critique, cela fonctionne, sinon le partage d'un tableau peut avoir sa propre raison.
Emilio Garavaglia
4
Ensuite, vous pouvez probablement utiliser shared_ptr<array<int, 6>>.
Timmmm
10
L'autre différence est qu'il est légèrement plus grand et plus lent qu'un tableau brut. Généralement pas vraiment un problème mais ne prétendons pas que 1 == 1.1.
Andrew
2
Il existe des situations où la source des données dans le tableau signifie qu'il est difficile ou inutile de convertir en vecteur; comme lors de l'obtention d'un cadre à partir d'un appareil photo. (Ou, c'est ce que je comprends, de toute façon)
Narfanator