web-dev-qa-db-fra.com

Y a-t-il une utilité pour unique_ptr avec array?

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?

193
fen

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.

211
Nicol Bolas

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écution
  • array permet uniquement de spécifier la taille lors de la compilation

Redimensionnement

  • array et unique_ptr<T[]> n'autorisent pas le redimensionnement
  • vector fait

Espace 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'objet

Copier

  • array et vector permettent la copie
  • unique_ptr<T[]> n'autorise pas la copie

Swap/move

  • vector et unique_ptr<T[]> ont O(1) time swap et déplace les opérations
  • array a O(n) fois swap et déplace les opérations, n étant le nombre d'éléments dans le tableau

Invalidation 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 conteneurs
  • unique_ptr<T[]> n'est pas un conteneur

Je dois admettre que cela semble être une opportunité pour une refactorisation avec une conception basée sur des politiques.

100
Pseudonym

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.

59
Charles Salvia

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.

28
Andy Prowl

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, car std::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'un std::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[]>?

19
newling

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());
}
12
george

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...
...
9
Mr.C64

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). 

9
Simon Ferquel

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.

8
jorgbrown

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? :-)

4
  • Votre structure doit contenir uniquement un pointeur pour des raisons de compatibilité binaire.
  • Vous devez vous connecter avec une API qui renvoie la mémoire allouée avec new[]
  • Votre entreprise ou votre projet applique une règle générale contre l'utilisation de std::vector, par exemple, pour empêcher les programmeurs négligents d'introduire des copies par inadvertance.
  • Vous voulez empêcher les programmeurs négligents d'introduire accidentellement des copies dans ce cas.

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.

2
Jimmy Hartzell

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.

2
Simon Buchan

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.

2
Romain Laneuville

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.

2
Boris Shpungin

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).

1
Serge Rogatch

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.

0
Ilia Minkin