std::unique_ptr
supporta gli array, ad esempio:
std::unique_ptr<int[]> p(new int[10]);
ma è necessario? probabilmente è più comodo da usare std::vector
o std::array
.
Trovi utile per quel costrutto?
std::shared_ptr
<T []> è ora in c ++ 17.
std::unique_ptr
supporta gli array, ad esempio:
std::unique_ptr<int[]> p(new int[10]);
ma è necessario? probabilmente è più comodo da usare std::vector
o std::array
.
Trovi utile per quel costrutto?
std::shared_ptr
<T []> è ora in c ++ 17.
Risposte:
Alcune persone non hanno il lusso di usare std::vector
, anche con gli allocatori. Alcune persone hanno bisogno di un array di dimensioni dinamiche, quindi std::array
è fuori. E alcune persone ottengono i loro array da altri codici noti per restituire un array; e quel codice non verrà riscritto per restituire un vector
o qualcosa del genere.
Consentendo unique_ptr<T[]>
, soddisfate tali esigenze.
In breve, si usa unique_ptr<T[]>
quando è necessario . Quando le alternative semplicemente non funzioneranno per te. È uno strumento di ultima istanza.
vector
". Puoi discutere se si tratta di requisiti ragionevoli o meno, ma non puoi negare che esistano .
std::vector
se fosse possibile std::unique_ptr
.
unique_ptr
neanche questo, ma quel tipo di progetti esiste davvero.
Ci sono dei compromessi e scegli la soluzione che corrisponde a ciò che desideri. In cima alla mia testa:
Dimensione iniziale
vector
e unique_ptr<T[]>
consentire la dimensione da specificare in fase di esecuzionearray
consente solo la dimensione da specificare al momento della compilazioneRidimensionamento
array
e unique_ptr<T[]>
non consentire il ridimensionamentovector
faConservazione
vector
e unique_ptr<T[]>
archiviare i dati all'esterno dell'oggetto (in genere nell'heap)array
memorizza i dati direttamente nell'oggettocopiatura
array
e vector
consentire la copiaunique_ptr<T[]>
non consente la copiaSwap / mossa
vector
e unique_ptr<T[]>
avere O (1) swap
operazioni di tempo e spostamentoarray
ha O (n) swap
operazioni di tempo e spostamento, dove n è il numero di elementi nell'arrayAnnullamento puntatore / riferimento / iteratore
array
assicura che puntatori, riferimenti e iteratori non vengano mai invalidati mentre l'oggetto è attivo, anche su swap()
unique_ptr<T[]>
non ha iteratori; i puntatori e i riferimenti vengono invalidati solo swap()
mentre l'oggetto è attivo. (Dopo lo scambio, i puntatori puntano verso l'array con cui si è scambiato, quindi sono ancora "validi" in tal senso.)vector
può invalidare puntatori, riferimenti e iteratori su qualsiasi riallocazione (e fornisce alcune garanzie che la riallocazione può avvenire solo su determinate operazioni).Compatibilità con concetti e algoritmi
array
e vector
sono entrambi contenitoriunique_ptr<T[]>
non è un contenitoreDevo ammetterlo, questa sembra un'opportunità per alcuni refactoring con un design basato su criteri.
vector
. Quindi si aumenta la dimensione o la capacità di vector
tale da forzare una riallocazione. Quindi quell'iteratore, puntatore o riferimento non punta più a quell'elemento di vector
. Questo è ciò che intendiamo per "invalidazione". Questo problema non si verifica array
perché non esiste una "riallocazione". In realtà, ho appena notato un dettaglio con quello, e l'ho modificato per adattarlo.
unique_ptr<T[]>
perché non c'è riallocazione. Ma ovviamente, quando l'array non rientra nell'ambito, i puntatori a elementi specifici verranno comunque invalidati.
T[]
, le dimensioni (o informazioni equivalenti) devono essere sospese da qualche parte per operator delete[]
distruggere correttamente gli elementi dell'array. Sarebbe bello se il programmatore avesse accesso a quello.
Uno dei motivi per cui potresti usare a unique_ptr
è se non vuoi pagare il costo di runtime dell'inizializzazione del valore dell'array.
std::vector<char> vec(1000000); // allocates AND value-initializes 1000000 chars
std::unique_ptr<char[]> p(new char[1000000]); // allocates storage for 1000000 chars
Il std::vector
costruttore e std::vector::resize()
inizializzerà il valore, T
ma new
non lo farà se T
è un POD.
Vedi Oggetti inizializzati dal valore in C ++ 11 e costruttore std :: vector
Nota che vector::reserve
non è un'alternativa qui: l' accesso al puntatore non elaborato dopo std :: vector :: reserve è sicuro?
È la stessa ragione per cui un programmatore C potrebbe scegliere malloc
sopra calloc
.
std::vector
un allocatore personalizzato che eviti la costruzione di tipi che sono std::is_trivially_default_constructible
e la distruzione di oggetti che sono std::is_trivially_destructible
, sebbene ciò violi rigorosamente lo standard C ++ (poiché tali tipi non sono inizializzati di default).
std::unique_ptr
non fornisce alcun controllo associato contrario a molte std::vector
implementazioni.
std::vector
è richiesto dallo Standard per verificare i limiti .at()
. Immagino che intendessi dire che alcune implementazioni hanno anche modalità di debug che eseguiranno il check-in .operator[]
, ma lo considero inutile per scrivere un buon codice portatile.
Un std::vector
può essere copiato in giro, mentre unique_ptr<int[]>
consente di esprimere la proprietà unica dell'array. std::array
d'altra parte, richiede che la dimensione sia determinata in fase di compilazione, il che può essere impossibile in alcune situazioni.
unique_ptr
invece di shared_ptr
. Mi sto perdendo qualcosa?
unique_ptr
fa molto di più che prevenire un uso improprio accidentale. È anche più piccolo e più basso rispetto a shared_ptr
. Il punto è che, sebbene sia bello avere una semantica in una classe che previene "l'abuso", questa non è l'unica ragione per usare un tipo particolare. Ed vector
è molto più utile come memoria di array che unique_ptr<T[]>
, se non altro per il fatto che ha una dimensione .
vector
sopra unique_ptr<T[]>
, ove possibile, invece di dire: "non è possibile copiarlo" e quindi scegliere unique_ptr<T[]>
quando non si vuole copie. Impedire a qualcuno di fare la cosa sbagliata non è necessariamente il motivo più importante per scegliere una classe.
std::vector
ha un overhead maggiore di un std::unique_ptr
- usa ~ 3 puntatori invece di ~ 1. std::unique_ptr
blocca la costruzione della copia ma abilita la costruzione dello spostamento, che se semanticamente i dati con cui si sta lavorando possono essere spostati ma non copiati, infetta il class
contenuto dei dati. Fare un'operazione su dati non validi in realtà peggiora la classe del contenitore e "semplicemente non usarla" non lava via tutti i peccati. Dover mettere ogni tua istanza std::vector
in una classe in cui disabiliti manualmente move
è un mal di testa. std::unique_ptr<std::array>
ha un size
.
Scott Meyers ha questo da dire in Effective Modern C ++
L'esistenza di
std::unique_ptr
per le matrici devono considerarsi di interesse intellettuale a voi, perchéstd::array
,std::vector
,std::string
sono praticamente sempre le scelte migliori strutture dati di array grezzi. Sull'unica situazione che posso immaginare quandostd::unique_ptr<T[]>
potrebbe avere senso sarebbe quando si utilizza un'API di tipo C che restituisce un puntatore non elaborato a un array di heap di cui si assume la proprietà.
Penso che la risposta di Charles Salvia sia pertinente: std::unique_ptr<T[]>
è l'unico modo per inizializzare un array vuoto le cui dimensioni non sono note al momento della compilazione. Cosa direbbe Scott Meyers a proposito di questa motivazione per l'utilizzo std::unique_ptr<T[]>
?
vector
stackoverflow.com/a/24852984/2436175 .
Contrariamente a std::vector
e std::array
, std::unique_ptr
può possedere un puntatore NULL.
Ciò è utile quando si lavora con API C che prevedono un array o 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());
}
Ho usato unique_ptr<char[]>
per implementare un pool di memoria preallocato utilizzato in un motore di gioco. L'idea è di fornire pool di memoria preallocati utilizzati al posto di allocazioni dinamiche per restituire i risultati delle richieste di collisione e altre cose come la fisica delle particelle senza dover allocare / liberare memoria su ciascun frame. È abbastanza conveniente per questo tipo di scenari in cui sono necessari pool di memoria per allocare oggetti con durata di vita limitata (in genere uno, 2 o 3 frame) che non richiedono una logica di distruzione (solo deallocazione della memoria).
Un modello comune può essere trovato in alcune chiamate API Win32 di Windows , in cui l'uso std::unique_ptr<T[]>
può tornare utile, ad esempio quando non si sa esattamente quanto dovrebbe essere grande un buffer di output quando si chiama qualche API Win32 (che scriverà alcuni dati all'interno quel buffer):
// 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...
...
std::vector<char>
in questi casi.
Ho dovuto affrontare un caso in cui dovevo usare std::unique_ptr<bool[]>
, che si trovava nella libreria HDF5 (una libreria per un'efficiente memorizzazione dei dati binari, usata molto nella scienza). Alcuni compilatori (Visual Studio 2015 nel mio caso) forniscono la compressione distd::vector<bool>
(usando 8 bool in ogni byte), che è una catastrofe per qualcosa come HDF5, a cui non interessa quella compressione. Constd::vector<bool>
, HDF5 stava finalmente leggendo la spazzatura a causa di quella compressione.
Indovina chi era lì per il salvataggio, in un caso in cui std::vector
non ha funzionato, e ho dovuto allocare un array dinamico in modo pulito? :-)
In breve: è di gran lunga il più efficiente in termini di memoria.
A std::string
viene fornito con un puntatore, una lunghezza e un buffer di "ottimizzazione delle stringhe corte". Ma la mia situazione è che ho bisogno di memorizzare una stringa che è quasi sempre vuota, in una struttura di cui ho centinaia di migliaia. In C, userei solo char *
, e sarebbe null per la maggior parte del tempo. Che funziona anche per C ++, tranne per il fatto che a char *
non ha distruttori e non sa di cancellarsi. Al contrario, a std::unique_ptr<char[]>
si cancellerà da solo quando esce dal campo di applicazione. Un vuoto std::string
occupa 32 byte, ma un vuotostd::unique_ptr<char[]>
occupa 8 byte, ovvero esattamente la dimensione del suo puntatore.
Il più grande svantaggio è che, ogni volta che voglio sapere la lunghezza della stringa, devo fare appello strlen
.
Per rispondere alle persone che pensano di "dover" usare vector
invece di unique_ptr
un caso nella programmazione CUDA su GPU quando si alloca memoria in Dispositivo, è necessario scegliere un array di puntatori (con cudaMalloc
). Quindi, quando si recuperano questi dati in Host, è necessario andare di nuovo per un puntatore e unique_ptr
va bene per gestirlo facilmente. Il costo aggiuntivo della conversione double*
in vector<double>
non è necessario e porta a una perdita di perf.
Un motivo in più per consentire e utilizzare std::unique_ptr<T[]>
, che finora non è stato menzionato nelle risposte: consente di dichiarare in avanti il tipo di elemento array.
Ciò è utile quando si desidera ridurre al minimo le #include
istruzioni concatenate nelle intestazioni (per ottimizzare le prestazioni della build).
Per esempio -
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
Con la struttura del codice sopra, chiunque può #include "myclass.h"
e utilizzare MyClass
, senza dover includere le dipendenze di implementazione interne richieste da MyClass::m_InternalArray
.
Se m_InternalArray
invece fosse dichiarato rispettivamente come a std::array<ALargeAndComplicatedClassWithLotsOfDependencies>
, o a std::vector<...>
- il risultato sarebbe un tentativo di utilizzo di un tipo incompleto, che è un errore in fase di compilazione.
class ALargeAndComplicatedClassWithLotsOfDependencies
. Quindi logicamente non dovresti imbatterti in tali scenari.
Non posso essere abbastanza in disaccordo con lo spirito della risposta accettata. "Uno strumento di ultima istanza"? Lontano da esso!
Per come la vedo io, una delle caratteristiche più forti di C ++ rispetto a C e ad altri linguaggi simili è la capacità di esprimere i vincoli in modo che possano essere controllati in fase di compilazione e che sia possibile prevenire abusi accidentali. Quindi, quando si progetta una struttura, chiediti quali operazioni dovrebbe consentire. Tutti gli altri usi dovrebbero essere vietati, ed è meglio se tali restrizioni possono essere implementate staticamente (al momento della compilazione) in modo che l'uso improprio comporti un errore di compilazione.
Quindi, quando si ha bisogno di un array, le risposte alle seguenti domande specificano il suo comportamento: 1. Le sue dimensioni sono a) dinamiche in fase di esecuzione o b) statiche, ma note solo in fase di esecuzione o c) statiche e note al momento della compilazione? 2. L'array può essere allocato nello stack o no?
E in base alle risposte, questo è quello che vedo come la migliore struttura di dati per un tale array:
Dynamic | Runtime static | Static
Stack std::vector unique_ptr<T[]> std::array
Heap std::vector unique_ptr<T[]> unique_ptr<std::array>
Sì, penso unique_ptr<std::array>
dovrei anche essere considerato, e nessuno dei due è uno strumento di ultima istanza. Pensa solo a ciò che si adatta meglio al tuo algoritmo.
Tutti questi sono compatibili con le API C semplici tramite il puntatore non elaborato all'array di dati ( vector.data()
/ array.data()
/ uniquePtr.get()
).
PS Oltre alle considerazioni di cui sopra, ce n'è anche una di proprietà: std::array
e std::vector
ha una semantica di valore (ha un supporto nativo per la copia e il passaggio per valore), mentre unique_ptr<T[]>
può essere spostata (impone la proprietà singola). Entrambi possono essere utili in diversi scenari. Al contrario, gli array statici semplici ( int[N]
) e gli array dinamici semplici ( new int[10]
) non offrono nessuno dei due e quindi dovrebbero essere evitati, se possibile, il che dovrebbe essere possibile nella stragrande maggioranza dei casi. Se ciò non bastasse, anche le semplici matrici dinamiche non offrono alcun modo per interrogarne le dimensioni: un'opportunità in più per corruzione della memoria e falle di sicurezza.
Potrebbero essere la risposta più giusta possibile quando si arriva a colpire un singolo puntatore attraverso un'API esistente (messaggio di finestra think o parametri di callback relativi al threading) che hanno una certa misura della vita dopo essere stati "catturati" dall'altra parte del tratteggio, ma che non è correlato al codice chiamante:
unique_ptr<byte[]> data = get_some_data();
threadpool->post_work([](void* param) { do_a_thing(unique_ptr<byte[]>((byte*)param)); },
data.release());
Vogliamo tutti che le cose siano belle per noi. C ++ è per le altre volte.
unique_ptr<char[]>
può essere usato dove vuoi le prestazioni di C e la convenienza di C ++. Considera di dover operare su milioni (ok, miliardi se non ti fidi ancora) di stringhe. La memorizzazione di ciascuno di essi in un oggetto separato string
o vector<char>
sarebbe un disastro per le routine di gestione della memoria (heap). Soprattutto se è necessario allocare ed eliminare più stringhe diverse più volte.
Tuttavia, è possibile allocare un singolo buffer per la memorizzazione di tante stringhe. Non ti piacerebbe char* buffer = (char*)malloc(total_size);
per ovvi motivi (se non ovvio, cerca "why use smart ptrs"). Preferirestiunique_ptr<char[]> buffer(new char[total_size]);
Per analogia, le stesse considerazioni relative a prestazioni e convenienza si applicano ai non char
dati (considerare milioni di vettori / matrici / oggetti).
vector<char>
? La risposta, suppongo, è perché saranno inizializzati a zero quando si crea il buffer, mentre non lo saranno se si utilizza unique_ptr<char[]>
. Ma questo nugget chiave manca nella tua risposta.
new[]
std::vector
, ad esempio, per impedire ai programmatori negligenti di introdurre accidentalmente copieEsiste una regola generale secondo cui i contenitori C ++ devono essere preferiti al rolling-your-own con puntatori. È una regola generale; ha delle eccezioni. C'è più; questi sono solo esempi.
Se hai bisogno di un array dinamico di oggetti che non sono costruibili per la copia, allora un puntatore intelligente a un array è la strada da percorrere. Ad esempio, cosa succede se hai bisogno di una matrice di atomica.
std::shared_ptr<T[]>
, ma ci dovrebbe essere, e probabilmente sarà in C ++ 14 se qualcuno potrebbe essere disturbato a scrivere una proposta. Nel frattempo, c'è sempreboost::shared_array
.