std::unique_ptr
supporte les tableaux, par exemple:
std::unique_ptr<int[]> p(new int[10]);
mais est-ce nécessaire? Il est probablement plus pratique d’utiliser std::vector
ou std::array
.
Trouvez-vous une utilisation pour cette construction?
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 dynamique, donc std::array
est absent. Et certaines personnes obtiennent leurs tableaux d'un autre code connu pour renvoyer un tableau; et ce code ne sera pas réécrit pour renvoyer un vector
ou quelque chose.
En autorisant unique_ptr<T[]>
, vous répondez à ces besoins.
En bref, vous utilisez unique_ptr<T[]>
lorsque vous besoin à. Lorsque les solutions de rechange ne fonctionnent tout simplement pas pour vous. C'est un outil de dernier recours.
Il existe des compromis et vous choisissez la solution qui correspond à ce que vous souhaitez. Du haut de ma tête:
Dimension initiale
vector
et unique_ptr<T[]>
permettent de spécifier la taille au moment de l'exécutionarray
permet uniquement de spécifier la taille lors de la compilationRedimensionnement
array
et unique_ptr<T[]>
n'autorisent pas le redimensionnementvector
faitEspace de rangement
vector
et unique_ptr<T[]>
stockent les données en dehors de l'objet (généralement sur le tas)array
stocke les données directement dans l'objetCopier
array
et vector
permettent la copieunique_ptr<T[]>
n'autorise pas la copieSwap/move
vector
et unique_ptr<T[]>
ont O(1) time swap
et déplace les opérationsarray
a O(n) fois swap
et déplace les opérations, n étant le nombre d'éléments dans le tableauInvalidation 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 actif, même sur swap()
unique_ptr<T[]>
n'a pas d'itérateurs; les pointeurs et les références ne sont invalidés que par swap()
tant que l'objet est actif. (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 les pointeurs, les références et les itérateurs lors de toute réaffectation (et offre certaines garanties que la réaffectation ne peut avoir lieu que sur certaines opérations).Compatibilité avec les concepts et les algorithmes
array
et vector
sont tous deux des conteneursunique_ptr<T[]>
n'est pas un conteneurJe dois admettre que cela semble être une opportunité pour une refactorisation avec une conception basée sur des politiques.
Si vous ne voulez pas payer le coût d'exécution de value-initializing the array, vous pouvez utiliser l'un des unique_ptr
.
std::vector<char> vec(1000000); // allocates AND value-initializes 1000000 chars
std::unique_ptr<char[]> p(new char[1000000]); // allocates storage for 1000000 chars
Les constructeurs std::vector
et std::vector::resize()
initialisent T
- mais new
ne le fera pas si T
est un POD.
Voir Objets initialisés par valeur dans les constructeurs C++ 11 et std :: vector
Notez que vector::reserve
n'est pas une alternative ici: L'accès au pointeur brut après std :: vector :: reserve est-il sécurisé?
C'est la même raison pour laquelle un programmeur C peut choisir malloc
sur calloc
.
Un std::vector
peut être copié, alors que unique_ptr<int[]>
permet d’exprimer la propriété unique du tableau. std::array
, en revanche, nécessite que la taille soit déterminée au moment de la compilation, ce qui peut être impossible dans certaines situations.
Scott Meyers a ceci à dire dans Effective Modern C++
L'existence de
std::unique_ptr
pour les tableaux ne devrait vous intéresser que sur le plan intellectuel, carstd::array
,std::vector
,std::string
sont pratiquement toujours de meilleurs choix de structure de données que les tableaux bruts. La seule situation que je puisse concevoir lorsqu'unstd::unique_ptr<T[]>
serait logique serait lorsque vous utilisez une API de type C qui renvoie un pointeur brut à un tableau de tas dont vous assumez la propriété.
Je pense que la réponse de Charles Salvia est cependant pertinente: std::unique_ptr<T[]>
est le seul moyen d’initialiser un tableau vide dont la taille n’est pas connue au moment de la compilation. Qu'est-ce que Scott Meyers aurait à dire à propos de cette motivation à utiliser std::unique_ptr<T[]>
?
Contrairement à std::vector
et std::array
, std::unique_ptr
peut posséder un pointeur NULL.
Ceci est pratique lorsque vous travaillez avec des API C qui attendent un tableau ou NULL:
void legacy_func(const int *array_or_null);
void some_func() {
std::unique_ptr<int[]> ptr;
if (some_condition) {
ptr.reset(new int[10]);
}
legacy_func(ptr.get());
}
Un modèle commun peut être trouvé dans certains Windows appels Win32 API, dans lesquels l’utilisation de std::unique_ptr<T[]>
peut être pratique, par exemple. lorsque vous ne savez pas exactement quelle doit être la taille d'un tampon de sortie lorsque vous appelez une API Win32 (qui écrira des données à l'intérieur de ce tampon):
// Buffer dynamically allocated by the caller, and filled by some Win32 API function.
// (Allocation will be made inside the 'while' loop below.)
std::unique_ptr<BYTE[]> buffer;
// Buffer length, in bytes.
// Initialize with some initial length that you expect to succeed at the first API call.
UINT32 bufferLength = /* ... */;
LONG returnCode = ERROR_INSUFFICIENT_BUFFER;
while (returnCode == ERROR_INSUFFICIENT_BUFFER)
{
// Allocate buffer of specified length
buffer.reset( BYTE[bufferLength] );
//
// Or, in C++14, could use make_unique() instead, e.g.
//
// buffer = std::make_unique<BYTE[]>(bufferLength);
//
//
// Call some Win32 API.
//
// If the size of the buffer (stored in 'bufferLength') is not big enough,
// the API will return ERROR_INSUFFICIENT_BUFFER, and the required size
// in the [in, out] parameter 'bufferLength'.
// In that case, there will be another try in the next loop iteration
// (with the allocation of a bigger buffer).
//
// Else, we'll exit the while loop body, and there will be either a failure
// different from ERROR_INSUFFICIENT_BUFFER, or the call will be successful
// and the required information will be available in the buffer.
//
returnCode = ::SomeApiCall(inParam1, inParam2, inParam3,
&bufferLength, // size of output buffer
buffer.get(), // output buffer pointer
&outParam1, &outParam2);
}
if (Failed(returnCode))
{
// Handle failure, or throw exception, etc.
...
}
// All right!
// Do some processing with the returned information...
...
J'ai utilisé unique_ptr<char[]>
pour mettre en œuvre 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 éléments tels que la physique des particules sans avoir à allouer/libérer de la mémoire à chaque image. C'est assez pratique pour ce type de scénario où vous avez besoin de pools de mémoire pour allouer des objets à durée de vie limitée (généralement une, deux ou trois images) ne nécessitant pas de logique de destruction (uniquement l'allocation de mémoire).
En bref: c'est de loin le plus économe en mémoire.
Un std::string
est fourni avec un pointeur, une longueur et un tampon "optimisation de chaîne courte". Mais ma situation est la suivante: j'ai besoin de stocker une chaîne presque toujours vide, dans une structure dont j'ai des centaines de milliers. En C, je voudrais simplement utiliser char *
, et ce serait nul la plupart du temps. Ce qui fonctionne aussi pour C++, sauf qu'un char *
n'a pas de destructeur et ne sait pas se supprimer. En revanche, un std::unique_ptr<char[]>
se supprimera lui-même lorsqu'il sortira de sa portée. Un std::string
vide occupe 32 octets, mais un std::unique_ptr<char[]>
vide, 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 appeler strlen
.
J'ai été confronté à un cas où je devais utiliser std::unique_ptr<bool[]>
, qui se trouvait dans la bibliothèque HDF5 (une bibliothèque permettant un stockage efficace des données binaires, très utilisée en science). Certains compilateurs (Visual Studio 2015 dans mon cas) fournissent une compression de std::vector<bool>
(en utilisant 8 bools par octet), ce qui est une catastrophe pour quelque chose comme HDF5, qui se fiche de cette compression. Avec std::vector<bool>
, HDF5 lisait finalement des ordures à cause de cette compression.
Devinez qui était là pour le sauvetage, dans un cas où std::vector
ne fonctionnait pas et où j'avais besoin d'allouer un tableau dynamique proprement? :-)
new[]
std::vector
, par exemple, pour empêcher les programmeurs négligents d'introduire des copies par inadvertance.Il existe une règle générale selon laquelle les conteneurs C++ doivent être préférés aux rouleaux de votre propre avec des pointeurs. C'est une règle générale. il y a des exceptions. Il y a plus; Ce ne sont que des exemples.
C'est peut-être la solution la plus appropriée lorsque vous ne pouvez pointer qu'un seul pointeur via une API existante (paramètres de rappel de message de réflexion ou de threading liés à une fenêtre) qui possède une certaine mesure de la durée de vie après avoir été "attrapé" de l'autre côté de la hachure, mais qui n'est pas lié au code appelant:
unique_ptr<byte[]> data = get_some_data();
threadpool->post_work([](void* param) { do_a_thing(unique_ptr<byte[]>((byte*)param)); },
data.release());
Nous voulons tous que les choses soient agréables pour nous. C++ est pour les autres fois.
Pour répondre aux personnes qui pensent que vous "devez" utiliser vector
au lieu de unique_ptr
, la programmation CUDA sur GPU est un problème. Lorsque vous allouez de la mémoire dans Device, vous devez vous procurer un tableau de pointeurs (avec cudaMalloc
) . Ensuite, lors de l'extraction de ces données dans Host, vous devez redemander un pointeur et unique_ptr
convient très bien pour gérer le pointeur facilement . Le coût supplémentaire de la conversion de double*
en vector<double>
est inutile et conduit à une perte de performance.
Une autre raison d’autoriser et d’utiliser std::unique_ptr<T[]>
, qui n’a pas encore été mentionnée dans les réponses: elle vous permet de déclarer et de déclarer le type d’élément de tableau.
Ceci est utile lorsque vous souhaitez minimiser les instructions #include
chaînées dans les en-têtes (pour optimiser les performances de génération).
Par exemple -
myclass.h:
class ALargeAndComplicatedClassWithLotsOfDependencies;
class MyClass {
...
private:
std::unique_ptr<ALargeAndComplicatedClassWithLotsOfDependencies[]> m_InternalArray;
};
myclass.cpp:
#include "myclass.h"
#include "ALargeAndComplicatedClassWithLotsOfDependencies.h"
// MyClass implementation goes here
Avec la structure de code ci-dessus, tout le monde peut #include "myclass.h"
et utiliser MyClass
sans avoir à inclure les dépendances d'implémentation internes requises par MyClass::m_InternalArray
.
Si m_InternalArray
était à la place déclaré comme std::array<ALargeAndComplicatedClassWithLotsOfDependencies>
ou std::vector<...>
, respectivement, le résultat serait une tentative d'utilisation d'un type incomplet, ce qui est une erreur de compilation.
unique_ptr<char[]>
peut être utilisé là où vous voulez les performances du C et la commodité du C++. Considérez que vous devez utiliser des millions (ok, des milliards si vous ne faites pas encore confiance) de chaînes. Le stockage de chacun d'eux dans un objet string
ou vector<char>
distinct serait un sinistre pour les routines de gestion de la mémoire (tas). Surtout si vous avez besoin d'allouer et de supprimer plusieurs fois différentes chaînes.
Cependant, vous pouvez allouer un seul tampon pour stocker autant de chaînes. Vous ne voudriez pas char* buffer = (char*)malloc(total_size);
pour des raisons évidentes (sinon, cherchez "pourquoi utiliser smart ptrs"). Vous préférez plutôt unique_ptr<char[]> buffer(new char[total_size]);
Par analogie, les mêmes considérations de performance et de commodité s'appliquent aux données non -char
(prenons des millions de vecteurs/matrices/objets).
Si vous avez besoin d'un tableau dynamique d'objets qui ne peuvent pas être copiés, un pointeur intelligent sur un tableau est la solution. Par exemple, si vous avez besoin d’un tableau d’atomiques.