wake-up-neo.com

Gibt es eine Verwendung für unique_ptr mit Array?

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?

193
fen

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.

211
Nicol Bolas

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 Laufzeit
  • array erlaubt nur die Angabe der Größe zur Kompilierzeit

Größe ändern

  • array und unique_ptr<T[]> erlauben keine Größenänderung
  • vector tut

Lager

  • vector und unique_ptr<T[]> speichern die Daten außerhalb des Objekts (normalerweise auf dem Heap)
  • array speichert die Daten direkt im Objekt

Kopieren

  • array und vector erlauben das Kopieren
  • unique_ptr<T[]> erlaubt kein Kopieren

Tauschen/bewegen

  • vector und unique_ptr<T[]> haben O(1) Zeit swap und Verschiebungsoperationen
  • array hat O(n) Zeit swap und Verschiebungsoperationen, wobei n die Anzahl der Elemente im Array ist

Zeiger/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 Container
  • unique_ptr<T[]> ist kein Container

Ich muss zugeben, dies scheint eine Gelegenheit für Refactoring mit richtlinienbasiertem Design zu sein.

100
Pseudonym

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.

59
Charles Salvia

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.

28
Andy Prowl

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, da std::array, std::vector, std::string sind praktisch immer bessere Datenstrukturoptionen als unformatierte Arrays. Die einzige Situation, die ich mir vorstellen kann, wenn ein std::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?

19
newling

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

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

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

9
Simon Ferquel

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.

8
jorgbrown

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

  • Ihre Struktur muss aus Gründen der Binärkompatibilität nur einen Zeiger enthalten.
  • Sie müssen eine Schnittstelle mit einer API herstellen, die den mit new[] zugewiesenen Speicher zurückgibt.
  • In Ihrer Firma oder Ihrem Projekt gilt generell die Verwendung von std::vector, um zu verhindern, dass unachtsame Programmierer versehentlich Kopien einführen
  • Sie möchten verhindern, dass unvorsichtige Programmierer in diesem Fall versehentlich Kopien einführen.

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

2
Jimmy Hartzell

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.

2
Simon Buchan

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.

2

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.

2
Boris Shpungin

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

1
Serge Rogatch

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

0
Ilia Minkin