std::unique_ptr
unterstützt beispielsweise Arrays:
std::unique_ptr<int[]> p(new int[10]);
aber ist es nötig? wahrscheinlich ist es bequemer, std::vector
oder std::array
zu verwenden.
Findest du eine Verwendung für dieses Konstrukt?
Manche Leute haben nicht den Luxus, std::vector
zu verwenden, selbst mit Zuordnungssystemen. Einige Leute benötigen ein Array mit dynamischer Größe, daher ist std::array
out. Und einige Leute erhalten ihre Arrays von anderem Code, von dem bekannt ist, dass er ein Array zurückgibt. und dieser Code wird nicht umgeschrieben, um eine vector
oder etwas zurückzugeben.
Indem Sie unique_ptr<T[]>
zulassen, bedienen Sie diese Anforderungen.
Kurz gesagt, Sie verwenden unique_ptr<T[]>
, wenn Sie need to benötigen. Wenn die Alternativen für Sie einfach nicht funktionieren. Es ist ein Hilfsmittel der letzten Wahl.
Es gibt Kompromisse, und Sie wählen die Lösung, die Ihren Wünschen entspricht. Aus meinem Kopf:
Ursprüngliche Größe
vector
und unique_ptr<T[]>
ermöglichen die Angabe der Größe zur Laufzeitarray
erlaubt nur die Angabe der Größe zur KompilierzeitGröße ändern
array
und unique_ptr<T[]>
erlauben keine Größenänderungvector
tutLager
vector
und unique_ptr<T[]>
speichern die Daten außerhalb des Objekts (normalerweise auf dem Heap)array
speichert die Daten direkt im ObjektKopieren
array
und vector
erlauben das Kopierenunique_ptr<T[]>
erlaubt kein KopierenTauschen/bewegen
vector
und unique_ptr<T[]>
haben O(1) Zeit swap
und Verschiebungsoperationenarray
hat O(n) Zeit swap
und Verschiebungsoperationen, wobei n die Anzahl der Elemente im Array istZeiger/Referenz/Iterator Ungültigerklärung
array
sorgt dafür, dass Zeiger, Referenzen und Iteratoren niemals ungültig werden, solange das Objekt aktiv ist, selbst bei swap()
unique_ptr<T[]>
hat keine Iteratoren; Zeiger und Verweise werden nur von swap()
ungültig gemacht, solange das Objekt aktiv ist. (Nach dem Tauschen zeigen Zeiger auf das Array, mit dem Sie ausgetauscht wurden, sodass sie in diesem Sinne immer noch "gültig" sind.)vector
kann Zeiger, Referenzen und Iteratoren für jede Neuzuweisung ungültig machen (und gibt einige Garantien, dass eine Neuzuweisung nur bei bestimmten Vorgängen erfolgen kann).Kompatibilität mit Konzepten und Algorithmen
array
und vector
sind beide Containerunique_ptr<T[]>
ist kein ContainerIch muss zugeben, dies scheint eine Gelegenheit für Refactoring mit richtlinienbasiertem Design zu sein.
Ein Grund dafür, dass Sie einen unique_ptr
verwenden können, ist, wenn Sie die Laufzeitkosten für value-initializing des Arrays nicht bezahlen möchten.
std::vector<char> vec(1000000); // allocates AND value-initializes 1000000 chars
std::unique_ptr<char[]> p(new char[1000000]); // allocates storage for 1000000 chars
Der std::vector
-Konstruktor und std::vector::resize()
initialisieren T
-, new
jedoch nicht, wenn T
ein POD ist.
Siehe Wertinitialisierte Objekte in C++ 11 und std :: vector constructor
Beachten Sie, dass vector::reserve
hier keine Alternative ist: Ist der Zugriff auf den Rohzeiger nach std :: vector :: reserve sicher?
Es ist der gleiche Grund, warum ein C-Programmierer malloc
gegenüber calloc
wählen könnte.
Ein std::vector
kann rundherum kopiert werden, während unique_ptr<int[]>
den eindeutigen Besitz des Arrays ausweist. std::array
erfordert dagegen die Bestimmung der Größe zur Kompilierzeit, was in manchen Situationen unmöglich sein kann.
Scott Meyers hat dies in Effective Modern C++ zu sagen
Das Vorhandensein von
std::unique_ptr
für Arrays sollte nur für Sie von Interesse sein, dastd::array
,std::vector
,std::string
sind praktisch immer bessere Datenstrukturoptionen als unformatierte Arrays. Die einzige Situation, die ich mir vorstellen kann, wenn einstd::unique_ptr<T[]>
sinnvoll wäre, wäre, wenn Sie eine C-ähnliche API verwenden, die einen reinen Zeiger auf ein Heap-Array zurückgibt, dessen Besitz Sie übernehmen.
Ich denke, dass die Antwort von Charles Salvia relevant ist: Nur mit std::unique_ptr<T[]>
kann ein leeres Array initialisiert werden, dessen Größe zum Zeitpunkt des Kompilierens nicht bekannt ist. Was würde Scott Meyers zu dieser Motivation für die Verwendung von std::unique_ptr<T[]>
sagen?
Im Gegensatz zu std::vector
und std::array
kann std::unique_ptr
einen NULL-Zeiger besitzen.
Dies ist nützlich, wenn Sie mit C-APIs arbeiten, die entweder ein Array oder NULL erwarten:
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());
}
Ein allgemeines Muster kann in some Windows Win32 API - Aufrufen gefunden werden, bei denen die Verwendung von std::unique_ptr<T[]>
nützlich sein kann, z. Wenn Sie nicht genau wissen, wie groß ein Ausgabepuffer sein soll, wenn Sie eine Win32-API aufrufen (damit Daten in diesen Puffer geschrieben werden):
// 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...
...
Ich habe unique_ptr<char[]>
verwendet, um vorab zugewiesene Speicherpools zu implementieren, die in einer Spiel-Engine verwendet werden. Die Idee ist, vorab zugewiesene Speicherpools bereitzustellen, die anstelle von dynamischen Zuweisungen verwendet werden, um Kollisionsanforderungsergebnisse und anderes Material wie Teilchenphysik zurückzugeben, ohne dass bei jedem Frame Speicher zugewiesen oder freigegeben werden muss. Dies ist sehr praktisch für diese Art von Szenarien, in denen Sie Speicherpools benötigen, um Objekte mit begrenzter Lebensdauer (normalerweise ein, zwei oder drei Frames) zuzuordnen, für die keine Zerstörungslogik erforderlich ist (nur Speicherfreigabe).
Kurz gesagt: es ist bei weitem das speichereffizienteste.
Ein std::string
enthält einen Zeiger, eine Länge und einen "Short-String-Optimization" -Puffer. Aber meine Situation ist, dass ich einen String speichern muss, der fast immer leer ist, in einer Struktur, die ich Hunderttausende habe. In C würde ich einfach char *
verwenden, und es wäre meistens null. Das funktioniert auch für C++, außer dass ein char *
keinen Destruktor hat und sich nicht selbst löschen kann. Im Gegensatz dazu wird ein std::unique_ptr<char[]>
sich selbst löschen, wenn er den Gültigkeitsbereich verlässt. Ein leerer std::string
belegt 32 Bytes, ein leerer std::unique_ptr<char[]>
8 Bytes, also genau die Größe seines Zeigers.
Der größte Nachteil ist, jedes Mal, wenn ich die Länge der Zeichenfolge wissen möchte, muss ich strlen
darauf anrufen.
Ich stand vor einem Fall, in dem ich std::unique_ptr<bool[]>
verwenden musste, das sich in der HDF5-Bibliothek befand (eine Bibliothek für effiziente binäre Datenspeicherung, die in der Wissenschaft häufig verwendet wird). Einige Compiler (in meinem Fall Visual Studio 2015) bieten die Komprimierung von std::vector<bool>
(durch Verwendung von 8 Bools in jedem Byte), was für HDF5 eine Katastrophe ist, die sich nicht für diese Komprimierung interessiert. Mit std::vector<bool>
las HDF5 aufgrund dieser Komprimierung schließlich Müll.
Ratet mal, wer für die Rettung da war, in dem Fall, dass std::vector
nicht funktionierte und ich ein dynamisches Array sauber zuordnen musste? :-)
new[]
zugewiesenen Speicher zurückgibt.std::vector
, um zu verhindern, dass unachtsame Programmierer versehentlich Kopien einführenEs gibt eine allgemeine Regel, dass C++ - Container dem Verrollen mit Zeigern vorgezogen werden sollen. Das ist eine allgemeine Regel. es hat Ausnahmen. Es gibt mehr; Dies sind nur Beispiele.
Sie sind möglicherweise die richtige Antwort, wenn Sie nur einen einzelnen Zeiger durch eine vorhandene API (Think Window-Nachricht oder Threading-bezogene Callback-Parameter) ansteuern, die ein gewisses Maß an Lebensdauer haben, nachdem Sie auf der anderen Seite der Schraffur "abgefangen" wurden. was aber nicht mit dem aufrufenden Code zusammenhängt:
unique_ptr<byte[]> data = get_some_data();
threadpool->post_work([](void* param) { do_a_thing(unique_ptr<byte[]>((byte*)param)); },
data.release());
Wir alle möchten, dass die Dinge für uns schön sind. C++ ist für die anderen Zeiten.
Um zu antworten, dass Leute denken, dass Sie "müssen", vector
anstelle von unique_ptr
verwenden. Ich habe einen Fall in der CUDA-Programmierung auf GPU, wenn Sie Speicher in Device zuweisen, müssen Sie ein Zeigerarray (mit cudaMalloc
) In Host müssen Sie erneut einen Zeiger suchen, und unique_ptr
ist für die einfache Handhabung des Zeigers in Ordnung. Die zusätzlichen Kosten für die Konvertierung von double*
in vector<double>
sind unnötig und führen zu einem Perfomerverlust.
Ein weiterer Grund, std::unique_ptr<T[]>
zuzulassen und zu verwenden, der in den Antworten bisher nicht erwähnt wurde: Ermöglicht die Vorwärtsdeklarierung des Array-Elementtyps.
Dies ist nützlich, wenn Sie die verketteten #include
-Anweisungen in Headern minimieren möchten (um die Build-Leistung zu optimieren.)
Zum Beispiel -
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
Mit der obigen Codestruktur kann jeder #include "myclass.h"
und MyClass
verwenden, ohne die internen Implementierungsabhängigkeiten zu berücksichtigen, die von MyClass::m_InternalArray
erforderlich sind.
Wenn m_InternalArray
stattdessen als std::array<ALargeAndComplicatedClassWithLotsOfDependencies>
bzw. std::vector<...>
deklariert wurde, wird versucht, einen unvollständigen Typ zu verwenden, was einen Fehler bei der Kompilierung darstellt.
unique_ptr<char[]>
Kann verwendet werden, wenn Sie die Leistung von C und die Bequemlichkeit von C++ wünschen. Betrachten Sie, Sie müssen Millionen (ok, Milliarden, wenn Sie noch nicht vertrauen) von Zeichenfolgen bearbeiten. Das Speichern von jedem von ihnen in einem separaten Objekt string
oder vector<char>
Wäre eine Katastrophe für die Speicherverwaltungsroutinen (Heap). Vor allem, wenn Sie mehrere Male unterschiedliche Zeichenfolgen zuweisen und löschen müssen.
Sie können jedoch einen einzelnen Puffer zum Speichern so vieler Zeichenfolgen zuweisen. Sie würden char* buffer = (char*)malloc(total_size);
aus offensichtlichen Gründen nicht mögen (wenn nicht offensichtlich, suchen Sie nach "why use smart ptrs"). Sie möchten lieber unique_ptr<char[]> buffer(new char[total_size]);
Analog gelten die gleichen Überlegungen zu Leistung und Benutzerfreundlichkeit für Daten, die nicht char
sind (Millionen von Vektoren/Matrizen/Objekten berücksichtigen).
Wenn Sie ein dynamisches Array von Objekten benötigen, die nicht kopierkonstruierbar sind, ist ein intelligenter Zeiger auf ein Array der richtige Weg. Was ist zum Beispiel, wenn Sie ein Array von Atomen benötigen?.