std::unique_ptr
prend en charge les tableaux, par exemple:
std::unique_ptr<int[]> p(new int[10]);
mais est-ce nécessaire? il est probablement plus pratique à utiliser std::vector
ou std::array
.
Trouvez-vous une utilité pour cette construction?
c++
c++11
smart-pointers
unique-ptr
marais
la source
la source
std::shared_ptr<T[]>
, mais il devrait y en avoir, et il le sera probablement en C ++ 14 si quelqu'un peut se donner la peine de rédiger une proposition. En attendant, il y en a toujoursboost::shared_array
.std::shared_ptr
<T []> est maintenant en c ++ 17.Réponses:
Certaines personnes n'ont pas le luxe d'utiliser
std::vector
, même avec des allocateurs. Certaines personnes ont besoin d'un tableau de taille dynamiquestd::array
. Et certaines personnes obtiennent leurs tableaux à partir d'un autre code connu pour renvoyer un tableau; et ce code ne va pas être réécrit pour retourner unvector
ou quelque chose.En permettant
unique_ptr<T[]>
, vous répondez à ces besoins.En bref, vous utilisez
unique_ptr<T[]>
quand vous en avez besoin . Lorsque les alternatives ne fonctionnent tout simplement pas pour vous. C'est un outil de dernier recours.la source
vector
". Vous pouvez vous demander s'il s'agit d'exigences raisonnables ou non, mais vous ne pouvez pas nier qu'elles existent .std::vector
s'il le peutstd::unique_ptr
.unique_ptr
plus, mais ce genre de projets existe vraiment.Il y a des compromis et vous choisissez la solution qui correspond à ce que vous voulez. Du haut de ma tête:
Dimension initiale
vector
etunique_ptr<T[]>
autoriser la taille à spécifier au moment de l'exécutionarray
permet uniquement de spécifier la taille au moment de la compilationRedimensionnement
array
etunique_ptr<T[]>
ne permettent pas le redimensionnementvector
Est-ce queEspace de rangement
vector
etunique_ptr<T[]>
stocker les données en dehors de l'objet (généralement sur le tas)array
stocke les données directement dans l'objetCopier
array
etvector
autoriser la copieunique_ptr<T[]>
ne permet pas la copieÉchanger / déplacer
vector
etunique_ptr<T[]>
avoir O (1) tempsswap
et opérations de déplacementarray
a O (n) tempsswap
et opérations de déplacement, où n est le nombre d'éléments dans le tableauInvalidation du pointeur / référence / itérateur
array
garantit que les pointeurs, les références et les itérateurs ne seront jamais invalidés tant que l'objet est en direct, même surswap()
unique_ptr<T[]>
n'a pas d'itérateurs; les pointeurs et les références ne sont invalidésswap()
que lorsque l'objet est en direct. (Après l'échange, les pointeurs pointent vers le tableau avec lequel vous avez échangé, ils sont donc toujours "valides" dans ce sens.)vector
peut invalider des pointeurs, des références et des itérateurs sur toute réallocation (et fournit certaines garanties que la réallocation ne peut se produire que sur certaines opérations).Compatibilité avec les concepts et algorithmes
array
etvector
sont les deux conteneursunique_ptr<T[]>
n'est pas un conteneurJe dois admettre que cela ressemble à une opportunité pour une refactorisation avec une conception basée sur des politiques.
la source
vector
. Ensuite, vous augmentez la taille ou la capacité de celui-ci devector
sorte qu'il force une réallocation. Ensuite, cet itérateur, pointeur ou référence ne pointe plus vers cet élément duvector
. C'est ce que nous entendons par "invalidation". Ce problème ne se produit pasarray
, car il n'y a pas de "réallocation". En fait, je viens de remarquer un détail avec cela, et je l'ai modifié pour convenir.unique_ptr<T[]>
parce qu'il n'y a pas de réallocation. Mais bien sûr, lorsque le tableau sort de la portée, les pointeurs vers des éléments spécifiques seront toujours invalidés.T[]
, la taille (ou des informations équivalentes) doit être suspendue quelque part pouroperator delete[]
détruire correctement les éléments du tableau. Ce serait bien si le programmeur y avait accès.Une raison pour laquelle vous pourriez utiliser un
unique_ptr
est si vous ne voulez pas payer le coût d'exécution de l' initialisation de la valeur du tableau.Le
std::vector
constructeur etstd::vector::resize()
initialisera la valeurT
- maisnew
ne le fera pas s'ilT
s'agit d'un POD.Voir Objets à valeur initialisée en C ++ 11 et constructeur std :: vector
Notez que ce
vector::reserve
n'est pas une alternative ici: L'accès au pointeur brut après std :: vector :: reserve est-il sûr?C'est la même raison , un programmeur C pourrait choisir
malloc
pluscalloc
.la source
std::vector
un allocateur personnalisé qui évite la construction de types qui sontstd::is_trivially_default_constructible
et la destruction d'objets qui le sontstd::is_trivially_destructible
, bien que cela viole strictement la norme C ++ (puisque ces types ne sont pas initialisés par défaut).std::unique_ptr
ne fournit pas contraire lié à la vérification beaucoup destd::vector
mises en œuvre.std::vector
est requis par la norme pour vérifier les limites.at()
. Je suppose que vous vouliez dire que certaines implémentations ont des modes de débogage qui seront également enregistrés.operator[]
, mais je considère que cela est inutile pour écrire un bon code portable.Un
std::vector
peut être copié, tout enunique_ptr<int[]>
permettant d'exprimer la propriété unique de la baie.std::array
, d'autre part, nécessite que la taille soit déterminée au moment de la compilation, ce qui peut être impossible dans certaines situations.la source
unique_ptr
place deshared_ptr
. Suis-je en train de manquer quelque chose?unique_ptr
fait plus que simplement empêcher une mauvaise utilisation accidentelle. Il est également plus petit et plus bas queshared_ptr
. Le fait étant que, même s'il est agréable d'avoir une sémantique dans une classe qui empêche les "abus", ce n'est pas la seule raison d'utiliser un type particulier. Etvector
est beaucoup plus utile en tant que stockage en baie queunique_ptr<T[]>
, si ce n'est pour une raison autre que le fait qu'il a une taille .vector
plusunique_ptr<T[]>
possible, au lieu de simplement dire "vous ne pouvez pas le copier" et donc de choisirunique_ptr<T[]>
quand vous ne voulez pas de copies. Empêcher quelqu'un de faire la mauvaise chose n'est pas nécessairement la raison la plus importante pour choisir un cours.std::vector
a plus de frais généraux qu'unstd::unique_ptr
- il utilise ~ 3 pointeurs au lieu de ~ 1.std::unique_ptr
bloque la construction de copie mais active la construction de déplacement qui, si sémantiquement les données avec lesquelles vous travaillez ne peuvent être déplacées que non copiées, infecte leclass
contenant les données. Avoir une opération sur des données non valides aggrave en fait votre classe de conteneur, et "ne l'utilisez pas" ne nettoie pas tous les péchés. Devoir mettre chaque instance de votrestd::vector
dans une classe où vous désactivez manuellementmove
est un casse-tête.std::unique_ptr<std::array>
a unsize
.Scott Meyers a ceci à dire dans Effective Modern C ++
Je pense que la réponse de Charles Salvia est pertinente: c'est
std::unique_ptr<T[]>
le seul moyen d'initialiser un tableau vide dont la taille n'est pas connue au moment de la compilation. Que dirait Scott Meyers de cette motivation à utiliserstd::unique_ptr<T[]>
?la source
vector
stackoverflow.com/a/24852984/2436175 .Contrairement à
std::vector
etstd::array
,std::unique_ptr
peut posséder un pointeur NULL.Cela est pratique lorsque vous travaillez avec des API C qui attendent un tableau ou NULL:
la source
J'ai utilisé
unique_ptr<char[]>
pour implémenter un pool de mémoire préalloué utilisé dans un moteur de jeu. L'idée est de fournir des pools de mémoire préalloués utilisés au lieu d'allocations dynamiques pour renvoyer les résultats des demandes de collision et d'autres choses comme la physique des particules sans avoir à allouer / libérer de la mémoire à chaque trame. C'est assez pratique pour ce type de scénarios où vous avez besoin de pools de mémoire pour allouer des objets à durée de vie limitée (généralement une, 2 ou 3 images) qui ne nécessitent pas de logique de destruction (uniquement la désallocation de mémoire).la source
Un modèle commun peut être trouvé dans certains appels de l' API Windows Win32 , dans lesquels l'utilisation de
std::unique_ptr<T[]>
peut être utile, par exemple lorsque vous ne savez pas exactement la taille d'un tampon de sortie lors de l'appel d'une API Win32 (qui écrira des données à l'intérieur ce tampon):la source
std::vector<char>
dans ces cas.J'ai dû faire face à un cas où je devais utiliser
std::unique_ptr<bool[]>
, qui se trouvait dans la bibliothèque HDF5 (une bibliothèque pour un stockage efficace des données binaires, beaucoup utilisée en science). Certains compilateurs (Visual Studio 2015 dans mon cas) fournissent une compression destd::vector<bool>
(en utilisant 8 bools dans chaque octet), ce qui est une catastrophe pour quelque chose comme HDF5, qui ne se soucie pas de cette compression. Avecstd::vector<bool>
, HDF5 lisait finalement les ordures à cause de cette compression.Devinez qui était là pour le sauvetage, dans un cas où cela
std::vector
n'a pas fonctionné, et j'avais besoin d'allouer proprement un tableau dynamique? :-)la source
En un mot: c'est de loin le plus économe en mémoire.
A
std::string
est fourni avec un pointeur, une longueur et un tampon "d'optimisation de chaîne courte". Mais ma situation est que j'ai besoin de stocker une chaîne qui est presque toujours vide, dans une structure dont j'en ai des centaines de milliers. En C, j'utiliserais justechar *
, et ce serait nul la plupart du temps. Ce qui fonctionne aussi pour C ++, sauf que achar *
n'a pas de destructeur et ne sait pas se supprimer. En revanche, astd::unique_ptr<char[]>
se supprimera lorsqu'il sortira du champ d'application. Un videstd::string
prend 32 octets, mais un videstd::unique_ptr<char[]>
prend 8 octets, c'est-à-dire exactement la taille de son pointeur.Le plus gros inconvénient est que chaque fois que je veux connaître la longueur de la chaîne, je dois l'appeler
strlen
.la source
Pour répondre aux gens qui pensent que vous "devez" utiliser
vector
au lieu deunique_ptr
j'ai un cas dans la programmation CUDA sur GPU lorsque vous allouez de la mémoire dans l'appareil, vous devez opter pour un tableau de pointeurs (aveccudaMalloc
). Ensuite, lors de la récupération de ces données dans l'hôte, vous devez recommencer pour un pointeur etunique_ptr
c'est bien de gérer le pointeur facilement. Le coût supplémentaire de la conversiondouble*
envector<double>
est inutile et entraîne une perte de performance.la source
Une raison supplémentaire d'autoriser et d'utiliser
std::unique_ptr<T[]>
, qui n'a pas été mentionnée dans les réponses jusqu'à présent: elle vous permet de déclarer en avant le type d'élément du tableau.Ceci est utile lorsque vous souhaitez minimiser la chaîne
#include
instructions dans les en-têtes (pour optimiser les performances de génération.)Par exemple -
Avec la structure de code ci-dessus, n'importe qui peut
#include "myclass.h"
et peut utiliserMyClass
, sans avoir à inclure les dépendances d'implémentation internes requises parMyClass::m_InternalArray
.Si a
m_InternalArray
été déclaré à la place comme unstd::array<ALargeAndComplicatedClassWithLotsOfDependencies>
ou unstd::vector<...>
respectivement - le résultat serait une tentative d'utilisation d'un type incomplet, ce qui est une erreur au moment de la compilation.la source
class ALargeAndComplicatedClassWithLotsOfDependencies
. Donc, logiquement, vous ne devriez pas rencontrer de tels scénarios.Je ne peux pas être assez en désaccord avec l'esprit de la réponse acceptée. "Un outil de dernier recours"? Loin de là!
Selon moi, l'une des caractéristiques les plus fortes de C ++ par rapport à C et à d'autres langages similaires est la capacité d'exprimer des contraintes afin qu'elles puissent être vérifiées au moment de la compilation et qu'une mauvaise utilisation accidentelle puisse être évitée. Ainsi, lors de la conception d'une structure, demandez-vous quelles opérations elle devrait permettre. Toutes les autres utilisations doivent être interdites, et il est préférable que de telles restrictions puissent être implémentées statiquement (au moment de la compilation) afin qu'une mauvaise utilisation entraîne un échec de compilation.
Ainsi, lorsque l'on a besoin d'un tableau, les réponses aux questions suivantes spécifient son comportement: 1. Sa taille est-elle a) dynamique au moment de l'exécution, ou b) statique, mais uniquement connue au moment de l'exécution, ou c) statique et connue au moment de la compilation? 2. Le tableau peut-il être alloué sur la pile ou non?
Et sur la base des réponses, voici ce que je considère comme la meilleure structure de données pour un tel tableau:
Oui, je pense
unique_ptr<std::array>
devrait également être pris en considération, et ni l'un ni l'autre n'est un outil de dernier recours. Pensez simplement à ce qui correspond le mieux à votre algorithme.Tous ces éléments sont compatibles avec les API C simples via le pointeur brut vers le tableau de données (
vector.data()
/array.data()
/uniquePtr.get()
).PS En dehors des considérations ci-dessus, il y a aussi une propriété:
std::array
etstd::vector
ont une sémantique de valeur (ont un support natif pour la copie et le passage par valeur), alorsunique_ptr<T[]>
qu'elles ne peuvent être déplacées (applique la propriété unique). Les deux peuvent être utiles dans différents scénarios. Au contraire, les tableaux statiques simples (int[N]
) et les tableaux dynamiques simples (new int[10]
) n'offrent ni l'un ni l'autre et doivent donc être évités si possible - ce qui devrait être possible dans la grande majorité des cas. Si cela ne suffisait pas, les tableaux dynamiques simples n'offrent également aucun moyen d'interroger leur taille - une opportunité supplémentaire pour les corruptions de mémoire et les failles de sécurité.la source
Ils peuvent être la meilleure réponse possible lorsque vous ne piquez qu'un seul pointeur via une API existante (pensez à un message de fenêtre ou à des paramètres de rappel liés au threading) qui ont une certaine durée de vie après avoir été "pris" de l'autre côté de la trappe, mais qui n'est pas lié au code appelant:
Nous voulons tous que les choses soient agréables pour nous. C ++ est pour les autres fois.
la source
unique_ptr<char[]>
peut être utilisé là où vous voulez les performances de C et la commodité de C ++. Considérez que vous devez opérer sur des millions (ok, des milliards si vous ne faites pas encore confiance) de chaînes. Le stockage de chacun d'eux dans un objetstring
ou un élément distinctvector<char>
serait un désastre pour les routines de gestion de la mémoire (tas). Surtout si vous devez allouer et supprimer plusieurs fois différentes chaînes.Cependant, vous pouvez allouer un seul tampon pour stocker autant de chaînes. Vous
char* buffer = (char*)malloc(total_size);
n'aimeriez pas pour des raisons évidentes (si ce n'est pas évident, recherchez "pourquoi utiliser des ptr intelligents"). Vous préférezunique_ptr<char[]> buffer(new char[total_size]);
Par analogie, les mêmes considérations de performances et de commodité s'appliquent aux non-
char
données (considérons des millions de vecteurs / matrices / objets).la source
vector<char>
? La réponse, je suppose, est parce qu'ils seront initialisés à zéro lorsque vous créez le tampon, alors qu'ils ne le seront pas si vous utilisezunique_ptr<char[]>
. Mais ce nugget clé est absent de votre réponse.new[]
std::vector
, par exemple, pour empêcher les programmeurs imprudents d'introduire accidentellement des copiesIl existe une règle générale selon laquelle les conteneurs C ++ doivent être préférés au roulage avec des pointeurs. C'est une règle générale; il a des exceptions. Il y a plus; ce ne sont que des exemples.
la source
Si vous avez besoin d'un tableau dynamique d'objets qui ne sont pas constructibles par copie, alors un pointeur intelligent vers un tableau est le chemin à parcourir. Par exemple, que faire si vous avez besoin d'un tableau d'atomes.
la source