Der richtige Weg, um unique_ptr zu erstellen, das ein zugewiesenes Array enthält

74

Was ist der richtige Weg, um ein unique_ptr zu erstellen, das ein Array enthält, das dem freien Speicher zugewiesen ist? Visual Studio 2013 unterstützt dies standardmäßig, aber wenn ich gcc Version 4.8.1 unter Ubuntu verwende, treten Speicherlecks und undefiniertes Verhalten auf.

Das Problem kann mit diesem Code reproduziert werden:

#include <memory>
#include <string.h>

using namespace std;

int main()
{
    unique_ptr<unsigned char> testData(new unsigned char[16000]());

    memset(testData.get(),0x12,0);

    return 0;
}

Valgrind gibt diese Ausgabe:

==3894== 1 errors in context 1 of 1:
==3894== Mismatched free() / delete / delete []
==3894==    at 0x4C2BADC: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==3894==    by 0x400AEF: std::default_delete<unsigned char>::operator()(unsigned char*) const (unique_ptr.h:67)
==3894==    by 0x4009D0: std::unique_ptr<unsigned char, std::default_delete<unsigned char> >::~unique_ptr() (unique_ptr.h:184)
==3894==    by 0x4007A9: main (test.cpp:19)
==3894==  Address 0x5a1a040 is 0 bytes inside a block of size 16,000 alloc'd
==3894==    at 0x4C2AFE7: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==3894==    by 0x40075F: main (test.cpp:15)
lauw
quelle
11
Fügen Sie keine Krankheiten in Ihren Code ein. Verwenden Sie std::fill.
R. Martinho Fernandes

Antworten:

124

Verwendung der T[]Spezialisierung:

std::unique_ptr<unsigned char[]> testData(new unsigned char[16000]());

Beachten Sie, dass Sie in einer idealen Welt nicht explizit verwenden müssen, um a newzu instanziieren unique_ptr, um eine potenzielle Sicherheitslücke für Ausnahmen zu vermeiden. Zu diesem Zweck stellt Ihnen C ++ 14 die std::make_uniqueFunktionsvorlage zur Verfügung. Weitere Details finden Sie in diesem ausgezeichneten GOTW . Die Syntax lautet:

auto testData = std::make_unique<unsigned char[]>(16000);
Juanchopanza
quelle
1
Nur um sicherzugehen: Ist ein unique_ptrvon T[]geschaffen , wie oben gezeigt, rufen Sie den richtigen deleter (dh delete []) auf den Daten? Ich habe irgendwo ein Tutorial gesehen, in dem ein benutzerdefinierter Löscher angegeben wurde, daher frage ich mich, ob dies (noch) erforderlich ist oder ob alles ordnungsgemäß gelöscht wird.
J00hi
2
@ j00hi Ja, das tut es. Das ist der Grund für eine Spezialisierung.
Juanchopanza
1
@juanchopanza std :: make_unique <T []> ruft den Standardkonstruktor von T auf. Was passiert für grundlegende Datentypen wie für vorzeichenloses Zeichen oben? Initialisiert make_unique zero grundlegende Datentypen? Wenn ich die "testData" (oben) drucke, druckt es nichts.
Kartik Trivikram
2
@kartiktrivikram Dieser Wert initialisiert die Elemente, was für Typen mit einem Standardkonstruktor bedeutet, dass sie standardmäßig konstruiert sind, und für integrierte Typen bedeutet eine Nullinitialisierung.
Juanchopanza
38

Verwenden Sie die Array-Version:

auto testData = std::unique_ptr<unsigned char[]>{ new unsigned char[16000] };

Oder mit c ++ 14 eine bessere Form (VS2013 hat sie bereits):

auto testData = std::make_unique<unsigned char[]>( 16000 );
galop1n
quelle
12
Die 2 Aussagen sind nicht gleichwertig. Der zweite Wert initialisiert das Array, während der erste das Array nicht initialisiert erstellt. In diesem Sinne make_uniqueist nicht immer besser als Konstruktor Plus new. Dies bedeutet, dass für Anwendungsfälle, in denen keine Wertinitialisierung erforderlich ist und vermieden werden muss, da dies leistungskritisch ist, die Konstruktorversion besser ist. Beachten Sie, dass auto testData = unique_ptr<unsigned char[]> { new unsigned char[16000]() ];dies der make_uniqueVersion entspricht.
Maxschlepzig
14

Ein wahrscheinlich besserer Weg wäre, std::vector<unsigned char>stattdessen zu verwenden

#include <vector>
#include <string>

using namespace std;

int main()
{
    vector<unsigned char> testData(0x12, 0); // replaces your memset
    // bla    
}

Der Vorteil ist, dass dies viel weniger fehleranfällig ist und Sie Zugriff auf alle Arten von Funktionen wie einfache Iteration, Einfügung und automatische Neuzuweisung bei Erreichen der Kapazität erhalten.

Es gibt eine Einschränkung: Wenn Sie Ihre Daten viel verschieben, std::vectorkostet dies etwas mehr, da nicht nur der Anfang der Daten, sondern auch die Größe und Kapazität erfasst werden.

Hinweis: Sie memsettun nichts, weil Sie es mit einem Nullzählungsargument aufrufen.

TemplateRex
quelle
1
std::vectorist zweifellos weniger fehleranfällig als die manuelle Speicherverwaltung. Aber wenn das OP genug weiß, um a zu verwenden std::unique_ptr, würde ich sagen, dass der Vektor kein klarer Gewinner mehr ist. Es hat wahrscheinlich mindestens etwas mehr als das Dreifache des Speicherbedarfs. Was zählen kann, wenn Sie es beispielsweise im leistungskritischen Code als Wert (Verschieben) übergeben.
Angew ist nicht mehr stolz auf SO
2
@Angew Ein wahrscheinlicheres Szenario ist, dass das OP für die Zuweisung von Rohzeigern verwendet wurde, davon gehört hat unique_ptrund jetzt auf modernes C ++ "upgraden" möchte. Ihr Leistungsargument ist gut bekannt, sollte aber nur für den kritischsten Code von Bedeutung sein. unique_ptrplus memsetist fehleranfällig, insbesondere wenn dies auf Nicht-POD-Typen verallgemeinert wird.
TemplateRex
Wenn Sie bereit sind, die Behauptung zu schwächen (z. B. "Ein wahrscheinlich besserer Weg ...") oder den Größen- / Leistungsunterschied zu erwähnen, erhalten Sie eine positive Bewertung ;-)
Angew ist nicht mehr stolz auf SO
Ich sehe nicht ein, wie viel mehr das Verschieben der Daten kostet, weil Sie sie einfach als Referenz übergeben, was dem entspricht, was passieren würde, wenn Sie eine übergeben würden std::unique_ptr. Wenn Sie möchten , kopieren Sie die Daten dann kann man eine leichte (winzige) Overhead , aber Sie auch die Möglichkeit bekommen zu bewegen , die Daten gegebenenfalls.
Galik
@Galik Ich denke, die Kommentare von Angew, die ich einbinde, sind unkompliziert: a std::vectorist dreimal so groß wie a std::unique_ptr, aber es zahlt sich in Bezug auf Bequemlichkeit und Datenkapselung aus.
TemplateRex
1

Scheint wie ein Trottel, ich werde erklären, was ich meine

class Object {
private :
    static int count;
public :
    Object() {
        cout << "Object Initialized " << endl;
        count++;
    }
    ~Object() {
        cout << "Object destroyed " << endl;
    }
    int print()
    {
        cout << "Printing" << endl;
        return count;
    }
};

int Object::count = 0;

int main(int argc,char** argv)
{
    // This will create a pointer of Object
    unique_ptr<Object> up2 = make_unique<Object>();  
    up2->print();
    // This will create a pointer to array of Objects, The below two are same. 
    unique_ptr<Object[]> up1 = std::make_unique<Object[]>(30);
    Object obj[30];
    cout << up1.get()[8].print();
    cout << obj[8].print();

    // this will create a array of pointers to obj. 
        unique_ptr<Object*[]> up= std::make_unique<Object*[]>(30);
        up.get()[5] = new Object();
        unique_ptr<Object> mk = make_unique<Object>(*up.get()[5]);
        cout << up.get()[5]->print();

        unique_ptr<unique_ptr<Object>[]> up3 =  std::make_unique<unique_ptr<Object>[]>(20);
        up3.get()[5] = make_unique<Object>();

    return 0;
}

Ziel des Beitrags ist es, dass es versteckte kleine subtile Dinge gibt, die Sie verstehen müssen. Das Erstellen eines Objektarrays entspricht dem Objektarray von unique_ptr. Es wird nur dann einen Unterschied machen, wenn Sie es im Argument übergeben. Das Erstellen eines Arrays von Objektzeigern von unique_ptr ist ebenfalls nicht sehr nützlich. Daher müssen Sie in den meisten Szenarien nur unter zwei verwenden.

unique_ptr<Object> obj;
//and 
unique_ptr<unique_ptr<Object>[]>= make_unique<unique_ptr<Object>[]>(20);
Raghavendar Reddy
quelle
1
unsigned int size=16000;
std::unique_ptr<unsigned char[], std::default_delete<unsigned char[]>> pData(new unsigned char[size]);
Kompetentes Bit
quelle
4
Während dieser Code die Frage möglicherweise beantwortet, verbessert die Bereitstellung eines zusätzlichen Kontexts darüber, warum und / oder wie dieser Code die Frage beantwortet, ihren langfristigen Wert.
Adiga
1

Wahrscheinlich so etwas wie das Folgende?

using namespace std;

int size = get_size();
int const init_value = 123;

unique_ptr<int[]> p = make_unique<int[]>(size)
fill(p.get(), p.get() + size, init_value);
Bruin
quelle