STL-Vektoren mit nicht initialisiertem Speicher?

Ich schreibe eine innere Schleife, die struct s im zusammenhängenden Speicher platzieren muss. Ich weiß nicht, wie viele dieser struct s es vor der Zeit geben wird. Mein Problem ist, dass der vector von STL seine Werte auf 0 initialisiert. Also, egal was ich mache, entstehen mir die Kosten für die Initialisierung plus die Kosten für das Setzen der Member struct zu ihren Werten.

Gibt es eine Möglichkeit, die Initialisierung zu verhindern, oder gibt es einen STL-artigen Container mit resizierbarer zusammenhängender Speicherung und nicht initialisierten Elementen?

(Ich bin mir sicher, dass dieser Teil des Codes optimiert werden muss und ich bin mir sicher, dass die Initialisierung erhebliche Kosten verursacht.)

Siehe auch meine Kommentare unten für eine Klärung, wenn die Initialisierung auftritt.

EINIGE CODE:

void GetsCalledALot(int* data1, int* data2, int count) {
    int mvSize = memberVector.size()
    memberVector.resize(mvSize + count);//causes 0-initialization

    for (int i = 0; i < count; ++i) {
        memberVector[mvSize + i].d1 = data1[i];
        memberVector[mvSize + i].d2 = data2[i];
    }
}
0
hinzugefügt bearbeitet
Ansichten: 2
Eine weitere Klarstellung: Es ist nicht, dass der Konstruktor die Werte auf 0 initialisiert Es ist, dass die Größe Anrufe einfügen, was der Fall ist.
hinzugefügt der Autor Jim Hunziker, Quelle
Hinweis - Die Verwendung von reserve() ist keine Lösung, da Sie nicht legal auf die Daten zugreifen können, die sich an den Endstellen() und darüber befinden.
hinzugefügt der Autor Jim Hunziker, Quelle
Hinweis: Sie können nicht auf Daten zugreifen, die sowieso nicht initialisiert werden. Gleiches Problem für Vektor nach .end() und nicht initialisierte Mitglieder eines T []. Aber mit einem Vektor ist die Wahrscheinlichkeit, dass der Debugging-Code Ihnen das jetzt sagt. Der Array-Code schlägt auf dem Kunden-PC unbemerkt fehl.
hinzugefügt der Autor MSalters, Quelle
Siehe auch stackoverflow.com/q/7218574/1969455 für einen Allokator-Ansatz. (gut für POD-Typen)
hinzugefügt der Autor Matthäus Brandl, Quelle
Könnten Sie uns auch die struct-Deklaration geben? Vielen Dank... :-)
hinzugefügt der Autor paercebal, Quelle
Das ist eine gute Frage. Für einige Anwendungen ist es wichtig zu erkennen, dass std :: vector immer seine Elemente initialisiert, auch wenn es sich um Plain-Old-Data (POD) handelt.
hinzugefügt der Autor nobar, Quelle

14 Antworten

Also, hier ist das Problem, resize ist aufrufen aufrufen, die eine Kopie Konstruktion von einem standardmäßig konstruierten Element für jedes der neu hinzugefügten Elemente. Um dies auf 0 zu bringen, müssen Sie Ihren eigenen Standardkonstruktor und Ihren eigenen Kopierkonstruktor als leere Funktionen schreiben. Dies ist für Ihren Kopierkonstruktor eine sehr schlechte Idee , da es die internen Neuzuweisungsalgorithmen von std :: vector stört.

Zusammenfassung: Das wird mit std :: vector nicht möglich sein.

0
hinzugefügt
Das ist das eigentliche Problem. std :: vector sollte erkennen, dass es keine Initialisierung durchführen muss, wenn T einen trivialen Standardkonstruktor hat. Danke, dass Sie darauf hingewiesen haben, dass der Kopierkonstruktor hier unnötige Arbeit leistet.
hinzugefügt der Autor Eric Hein, Quelle

Um auf reserve() Antworten zu klären: Sie müssen reserve() in Verbindung mit push_back() verwenden. Auf diese Weise wird der Standardkonstruktor nicht für jedes Element, sondern für den Kopierkonstruktor aufgerufen. Sie müssen immer noch die Strafe dafür zahlen, dass Sie Ihre Struktur auf dem Stack einrichten und sie dann in den Vektor kopieren. Auf der anderen Seite ist es möglich, dass wenn Sie verwenden

vect.push_back(MyStruct(fieldValue1, fieldValue2))

Der Compiler erstellt die neue Instanz direkt im Speicher, der zum Vektor gehört. Es hängt davon ab, wie schlau der Optimierer ist. Sie müssen den generierten Code überprüfen, um es herauszufinden.

0
hinzugefügt
Es stellt sich heraus, dass der Optimierer für gcc auf Stufe O3 nicht intelligent genug ist, um die Kopie zu vermeiden.
hinzugefügt der Autor Jim Hunziker, Quelle

C ++ 0x fügt eine neue Memberfunktionsvorlage emplace_back zu vector hinzu (die auf variadischen Vorlagen und perfekter Weiterleitung beruht), die alle Provisorien vollständig beseitigt:

memberVector.emplace_back(data1[i], data2[i]);
0
hinzugefügt

In C ++ 11 (und Boost) können Sie die Array-Version von unique_ptr verwenden, um ein nicht initialisiertes Array zuzuweisen. Dies ist kein stl Container, aber ist immer noch Speicher verwaltet und C ++ - ish, die für viele Anwendungen gut genug sein wird.

auto my_uninit_array = std::unique_ptr(new mystruct[count]);
0
hinzugefügt

Verwenden Sie die Methode std :: vector :: reserve (). Der Vektor wird nicht skaliert, aber der Speicherplatz wird zugewiesen.

0
hinzugefügt

Ähm ...

probiere die Methode aus:

std::vector::reserve(x)

Es ermöglicht Ihnen, genügend Speicherplatz für x-Objekte zu reservieren, ohne sie zu initialisieren (Ihr Vektor ist noch leer). Daher wird es keine Neuzuweisung geben, bis über x hinausgegangen wird.

Der zweite Punkt ist, dass der Vektor die Werte nicht auf Null initialisiert. Testen Sie Ihren Code im Debug-Modus?

Nach der Überprüfung auf g ++, der folgende Code:

#include 
#include 

struct MyStruct
{
   int m_iValue00 ;
   int m_iValue01 ;
} ;

int main()
{
   MyStruct aaa, bbb, ccc ;

   std::vector aMyStruct ;

   aMyStruct.push_back(aaa) ;
   aMyStruct.push_back(bbb) ;
   aMyStruct.push_back(ccc) ;

   aMyStruct.resize(6) ;//[EDIT] double the size

   for(std::vector::size_type i = 0, iMax = aMyStruct.size(); i < iMax; ++i)
   {
      std::cout << "[" << i << "] : " << aMyStruct[i].m_iValue00 << ", " << aMyStruct[0].m_iValue01 << "\n" ;
   }

   return 0 ;
}

gibt folgende Ergebnisse:

[0] : 134515780, -16121856
[1] : 134554052, -16121856
[2] : 134544501, -16121856
[3] : 0, -16121856
[4] : 0, -16121856
[5] : 0, -16121856

Die Initialisierung, die Sie gesehen haben, war wahrscheinlich ein Artefakt.

[EDIT] Nach dem Kommentar zur Größenanpassung habe ich den Code geändert, um die Größe zu ändern. Die Größenänderung ruft effektiv den Standardkonstruktor des Objekts innerhalb des Vektors auf, aber wenn der Standardkonstruktor nichts tut, dann wird nichts initialisiert ... Ich glaube immer noch, dass es ein Artefakt war (ich schaffte es zum ersten Mal, den gesamten Vektor mit der Null zu zerlegen) folgender Code:

aMyStruct.push_back(MyStruct()) ;
aMyStruct.push_back(MyStruct()) ;
aMyStruct.push_back(MyStruct()) ;

Damit... : - /

[EDIT 2] Wie bereits von Arkadiy angeboten, besteht die Lösung darin, einen Inline-Konstruktor zu verwenden, der die gewünschten Parameter verwendet. Etwas wie

struct MyStruct
{
   MyStruct(int p_d1, int p_d2) : d1(p_d1), d2(p_d2) {}
   int d1, d2 ;
} ;

Dies wird wahrscheinlich in Ihrem Code inline werden.

Aber Sie sollten Ihren Code trotzdem mit einem Profiler untersuchen, um sicherzustellen, dass dieser Code der Flaschenhals Ihrer Anwendung ist.

0
hinzugefügt
Ich habe oben eine Notiz geschrieben. Es ist nicht der Konstruktor des Vektors, der auf 0 initialisiert wird. Das ist resize ().
hinzugefügt der Autor Jim Hunziker, Quelle
Ich denke du bist auf dem richtigen Weg. Ich habe keinen Konstruktor in der Struktur definiert, daher wird der Standardkonstruktor (glaube ich) auf Null initialisiert. Ich überprüfe, ob das Hinzufügen eines Standardkonstruktors, der nichts tut, das Problem löst.
hinzugefügt der Autor Jim Hunziker, Quelle
Greg Rogers hat Recht. Meine Vermutung ist, dass der Speicher wegen einer Prozessinitialisierung unabhängig von dem Code, den ich geschrieben habe, "Null" war. In C ++ bezahlen Sie nicht für etwas, das Sie nicht verwenden. Wenn Sie C-ähnlichen Code schreiben, sollten Sie keinen Overhead haben. Und Vektoren sind ziemlich gut darin.
hinzugefügt der Autor paercebal, Quelle
@nobar: Es hängt vom MyStruct-Konstruktor ab. Wenn es leer und inlined ist und MyStruct-Member einen Konstruktor mit null Kosten haben, dann wird der C ++ - Compiler es auf nichts optimieren. Dann werden wir nicht dafür bezahlen. Nur für die Größenänderung.
hinzugefügt der Autor paercebal, Quelle
Es scheint, dass der Vektor uns in diesem Fall im Stich gelassen hat. Wir zahlen für die Initialisierung, auch wenn wir sie nicht brauchen oder wollen. Dies wird durch die Semantik von insert() garantiert, die von resize() aufgerufen wird. Der Wert, der für die Initialisierung verwendet wird, basiert auf dem, was sich in MyStruct befindet, das an resize() übergeben wurde. Da Sie beim Aufruf von resize() nichts angegeben haben, wurde der Standardkonstruktor verwendet. Da der Standardkonstruktor in diesem Fall nichts tut, erhalten Sie möglicherweise Nullen oder Sie erhalten etwas anderes. In
hinzugefügt der Autor nobar, Quelle
Wenn Sie keinen Konstruktor definiert haben und alle Elemente POD-Typen sind, führt der Konstruktor nichts aus. Wenn Elemente keine PODs sind, würden sie einfach ihre Standardkonstruktoren aufrufen.
hinzugefügt der Autor Greg Rogers, Quelle
In diesem Fall hat MyStruct einen trivialen Konstruktor, so dass nichts initialisiert wird. Dies kann sich von der Situation des OP unterscheiden.
hinzugefügt der Autor Greg Rogers, Quelle

Müssen die Strukturen selbst im zusammenhängenden Speicher sein, oder können Sie mit einem Vektor von struct * durchkommen?

Vektoren machen eine Kopie von allem, was Sie hinzufügen, also ist die Verwendung von Zeigervektoren anstelle von Objekten eine Möglichkeit, die Leistung zu verbessern.

0
hinzugefügt
Sie müssen zusammenhängend sein. Sie sind in einem Puffer, der über das Netzwerk als ein riesiger Brocken gesendet werden soll.
hinzugefügt der Autor Jim Hunziker, Quelle

Ich glaube nicht, dass STL deine Antwort ist. Sie müssen Ihre eigene Art von Lösung mit realloc() rollen. Sie müssen einen Zeiger und entweder die Größe oder die Anzahl der Elemente speichern und anhand dieser ermitteln, wo nach einem realloc() mit dem Hinzufügen von Elementen begonnen werden soll.

int *memberArray;
int arrayCount;
void GetsCalledALot(int* data1, int* data2, int count) {
    memberArray = realloc(memberArray, sizeof(int) * (arrayCount + count);
    for (int i = 0; i < count; ++i) {
        memberArray[arrayCount + i].d1 = data1[i];
        memberArray[arrayCount + i].d2 = data2[i];
    }
    arrayCount += count;
}
0
hinzugefügt

Von deinen Kommentaren zu anderen Postern sieht es so aus, als ob du malloc() und Freunde übrig hättest. Mit Vector können Sie keine unstrukturierten Elemente haben.

0
hinzugefügt

Aus Ihrem Code sieht es so aus, als hätten Sie einen Vektor von Strukturen, von denen jede 2 Ints enthält. Könnten Sie stattdessen 2 Vektoren von Ints verwenden? Dann

copy(data1, data1 + count, back_inserter(v1));
copy(data2, data2 + count, back_inserter(v2));

Jetzt zahlen Sie nicht für das Kopieren einer Struktur jedes Mal.

0
hinzugefügt
Interessant. Dies könnte einfach funktionieren - es scheint, als würde es die Konstruktion eines Zwischenobjekts vermeiden.
hinzugefügt der Autor nobar, Quelle

Ich würde etwas tun wie:

void GetsCalledALot(int* data1, int* data2, int count)
{
  const size_t mvSize = memberVector.size();
  memberVector.reserve(mvSize + count);

  for (int i = 0; i < count; ++i) {
    memberVector.push_back(MyType(data1[i], data2[i]));
  }
}

Sie müssen einen Ctor für den Typ definieren, der im memberVector gespeichert ist, aber das sind kleine Kosten, da Sie das Beste aus beiden Welten erhalten. Es wird keine unnötige Initialisierung durchgeführt und es findet während der Schleife keine Neuzuweisung statt.

0
hinzugefügt
Dies scheint das Problem nicht zu lösen, da es eine temporäre MyType() verwendet und diese in den Vektor kopiert. Es gibt immer noch eine doppelte Initialisierung.
hinzugefügt der Autor nobar, Quelle

std::vector must initialize the values in the array somehow, which means some constructor (or copy-constructor) must be called. The behavior of vector (or any container class) is undefined if you were to access the uninitialized section of the array as if it were initialized.

Der beste Weg ist, reserve() und push_back() zu verwenden, so dass der copy-constructor verwendet wird und die Standardkonstruierung vermieden wird.

Verwenden Sie Ihren Beispielcode:

struct YourData {
    int d1;
    int d2;
    YourData(int v1, int v2) : d1(v1), d2(v2) {}
};

std::vector memberVector;

void GetsCalledALot(int* data1, int* data2, int count) {
    int mvSize = memberVector.size();

   //Does not initialize the extra elements
    memberVector.reserve(mvSize + count);

   //Note: consider using std::generate_n or std::copy instead of this loop.
    for (int i = 0; i < count; ++i) {
       //Copy construct using a temporary.
        memberVector.push_back(YourData(data1[i], data2[i]));
    }
}

Das einzige Problem beim Aufruf von reserve() (oder resize() ) ist, dass Sie den copy-constructor öfter als nötig aufrufen können. Wenn Sie eine gute Vorhersage über die endgültige Größe des Arrays machen können, ist es besser, reserve() den Speicherplatz einmal am Anfang. Wenn Sie die endgültige Größe nicht kennen, wird zumindest die Anzahl der Kopien im Durchschnitt minimal sein.

In the current version of C++, the inner loop is a bit inefficient as a temporary value is constructed on the stack, copy-constructed to the vectors memory, and finally the temporary is destroyed. However the next version of C++ has a feature called R-Value references (T&&) which will help.

Die von std :: vector bereitgestellte Schnittstelle lässt keine andere Option zu, nämlich eine Factory-ähnliche Klasse zu verwenden, um andere Werte als die Standardwerte zu erstellen. Hier ist ein grobes Beispiel dafür, wie dieses Muster in C ++ implementiert aussehen würde:

template 
class my_vector_replacement {

   //...

    template 
    my_vector::push_back_using_factory(F factory) {
       //... check size of array, and resize if needed.

       //Copy construct using placement new,
        new(arrayData+end) T(factory())
        end += sizeof(T);
    }

    char* arrayData;
    size_t end;//Of initialized data in arrayData
};

// One of many possible implementations
struct MyFactory {
    MyFactory(int* p1, int* p2) : d1(p1), d2(p2) {}
    YourData operator()() const {
        return YourData(*d1,*d2);
    }
    int* d1;
    int* d2;
};

void GetsCalledALot(int* data1, int* data2, int count) {
   //... Still will need the same call to a reserve() type function.

   //Note: consider using std::generate_n or std::copy instead of this loop.
    for (int i = 0; i < count; ++i) {
       //Copy construct using a factory
        memberVector.push_back_using_factory(MyFactory(data1+i, data2+i));
    }
}

Dies bedeutet, dass Sie Ihre eigene Vektorklasse erstellen müssen. In diesem Fall kompliziert es auch, was ein einfaches Beispiel hätte sein sollen. Es kann jedoch vorkommen, dass die Verwendung einer Factory-Funktion wie dieser besser ist, zum Beispiel wenn die Einfügung von einem anderen Wert abhängig ist, und Sie andernfalls bedingungslos einige teure temporäre Konstrukte konstruieren müssten, selbst wenn sie nicht wirklich benötigt würden.

0
hinzugefügt

Wenn Sie wirklich darauf bestehen, dass die Elemente nicht initialisiert werden und einige Methoden wie front (), back (), push_back() geopfert werden, verwenden Sie Boost-Vektor von numerisch. Es ermöglicht Ihnen sogar nicht vorhandene Elemente beizubehalten, wenn Sie resize() aufrufen ...

0
hinzugefügt

Sie können einen Wrappertyp um Ihren Elementtyp herum verwenden, mit einem Standardkonstruktor, der nichts tut. Z.B.:

template 
struct no_init
{
    T value;

    no_init() { static_assert(std::is_standard_layout>::value && sizeof(T) == sizeof(no_init), "T does not have standard layout"); }

    no_init(T& v) { value = v; }
    T& operator=(T& v) { value = v; return value; }

    no_init(no_init& n) { value = n.value; }
    no_init(no_init&& n) { value = std::move(n.value); }
    T& operator=(no_init& n) { value = n.value; return this; }
    T& operator=(no_init&& n) { value = std::move(n.value); return this; }

    T* operator&() { return &value; }//So you can use &(vec[0]) etc.
};

Benutzen:

std::vector> vec;
vec.resize(2ul * 1024ul * 1024ul * 1024ul);
0
hinzugefügt