std :: vector :: resize () vs. std :: vector :: Reserve ()

80

Es ist ein Thread in den Kommentaren in diesem Beitrag über die Verwendung von std::vector::reserve()vs. std::vector::resize().

Hier ist der Originalcode:

void MyClass::my_method()
{
    my_member.reserve(n_dim);
    for(int k = 0 ; k < n_dim ; k++ )
         my_member[k] = k ;
}

Ich glaube, um Elemente in das zu schreiben vector, ist es richtig, anzurufen std::vector::resize(), nicht std::vector::reserve().

Tatsächlich "stürzt" der folgende Testcode in Debug-Builds in VS2010 SP1 ab:

#include <vector>

using namespace std;

int main()
{
    vector<int> v;
    v.reserve(10);
    v[5] = 2;

    return 0;
}

Habe ich recht oder irre ich mich? Und ist VS2010 SP1 richtig oder falsch?

Mr.C64
quelle
11
Die Erklärung könnte so einfach sein wie "Ich habe mich geirrt": D
Luchian Grigore
7
Ich habe dies als "zu lokalisiert" gekennzeichnet, da @LuchianGrigore selten falsch ist
Standard
1
@ Default las "selten falsch" als "schnell in der Korrektur seiner Fehler" :)
Luchian Grigore
1
Der Code im ursprünglichen Beitrag wurde aktualisiert, um ihn korrekt zu verwenden resize(), und der Zweifel wurde ausgeräumt. An Moderatoren: Sie können diese Frage jederzeit löschen, wenn sie "zu lokalisiert" ist, oder sie behalten, wenn Sie der Meinung sind, dass sie in Zukunft jemand anderem helfen könnte.
Mr.C64
1
Diese Frage klärt tatsächlich meine Zweifel, wenn ich mein Projekt von vc6 auf vs2013 migriere. Danke :))
pengMiao

Antworten:

112

Es gibt zwei verschiedene Methoden aus einem Grund:

std::vector::reserve ordnet den Speicher zu, ändert jedoch nicht die Größe Ihres Vektors, der eine logische Größe wie zuvor hat.

std::vector::resizeändert tatsächlich die Größe Ihres Vektors und füllt jeden Raum mit Objekten in ihrem Standardzustand. Wenn sie Ints sind, sind sie alle Null.

Nach der Reserve benötigen Sie in Ihrem Fall viele push_backs, um in Element 5 zu schreiben. Wenn Sie dies nicht möchten, sollten Sie in Ihrem Fall die Größenänderung verwenden.

Eine Sache über Reserve: Wenn Sie dann Elemente mit push_back hinzufügen, bleiben alle vorhandenen Referenzen, Iteratoren oder Zeiger auf Daten in Ihrem Vektor gültig, bis Sie die von Ihnen reservierte Kapazität erreicht haben. Wenn ich also 1000 reserviere und meine Größe 5 ist, &vec[4]bleibt die gleiche, bis der Vektor 1000 Elemente hat. Danach kann ich aufrufen push_back()und es wird funktionieren, aber der gespeicherte Zeiger von &vec[4]früher ist möglicherweise nicht mehr gültig.

Goldesel
quelle
Für einen leeren Vektor, dh vec, endet vec [1] nach der Reserve mit einem Segmentfehler.
Hagelzenzeng
2
vec [1] wäre undefiniertes Verhalten.
CashCow
Wird std::vector::reservedas gelegentliche Kopieren des vollständigen Arrays verhindern auf push_back?
Post Self
Ist dies nur für C ++ 11 oder eine bestimmte Standardimplementierung? Es sieht so aus, als ob der Code mit Reserve und Zugriff mit [] gut funktioniert. godbolt.org/z/MhgFdZ
Steve_Corrin
1
@Steve_Corrin Undefiniertes Verhalten ist undefiniert. Es kann scheinen zu funktionieren. Es ist immer noch ungültiger Code. Die Indizierung von Elementen, die nicht im < size()Container vorhanden sind, ist nicht zulässig. Sie existieren dort nach der Definition der Sprache nicht. Wenn Ihr Compiler beschließt, die Atomwaffen nicht zu starten und stattdessen nur den RAM so anstößt, wie Sie es möchten, ist das einfach viel Glück. Oder Pech, denke ich; Im Idealfall könnten wir alle ungültigen Dinge abfangen, die alle Programmierer tun würden, aber viel Glück, jemals dort anzukommen!
underscore_d
25

Hier beantwortet von Jan Hudec : Wahl zwischen vector :: resize () und vector :: Reserve ()

Die beiden Funktionen machen sehr unterschiedliche Dinge.

Die resize () -Methode (und die Übergabe des Arguments an den Konstruktor entspricht dieser) fügt eine bestimmte Anzahl von Elementen in den Vektor ein (es gibt ein optionales zweites Argument, um ihren Wert anzugeben). Dies wirkt sich auf die Größe () aus, die Iteration durchläuft alle diese Elemente, push_back wird nach ihnen eingefügt und Sie können mit dem Operator [] direkt darauf zugreifen.

Die Reserve () -Methode weist nur Speicher zu, lässt ihn jedoch nicht initialisiert. Dies wirkt sich nur auf die Kapazität () aus, die Größe () bleibt jedoch unverändert. Es gibt keinen Wert für die Objekte, da dem Vektor nichts hinzugefügt wird. Wenn Sie dann die Elemente einfügen, erfolgt keine Neuzuweisung, da dies im Voraus erfolgt ist. Dies ist jedoch der einzige Effekt.

Es kommt also darauf an, was Sie wollen. Wenn Sie ein Array mit 1000 Standardelementen möchten, verwenden Sie resize (). Wenn Sie ein Array möchten, in das Sie voraussichtlich 1000 Elemente einfügen möchten, und einige Zuordnungen vermeiden möchten, verwenden Sie Reserve ().

BEARBEITEN: Durch den Kommentar von Blastfurnace habe ich die Frage erneut gelesen und festgestellt, dass in Ihrem Fall die richtige Antwort nicht manuell vorab zugewiesen wird. Fügen Sie die Elemente am Ende einfach nach Bedarf ein. Der Vektor wird bei Bedarf automatisch neu zugewiesen und effizienter als auf die erwähnte manuelle Weise. Der einzige Fall, in dem Reserve () sinnvoll ist, ist, wenn Sie eine einigermaßen genaue Schätzung der Gesamtgröße haben, die Sie benötigen, und die im Voraus leicht verfügbar ist.

EDIT2: Anzeigenfrage bearbeiten: Wenn Sie eine anfängliche Schätzung haben, reservieren Sie diese Schätzung () und wenn sich herausstellt, dass sie nicht ausreicht, lassen Sie den Vektor einfach das tun.

lucasg
quelle
12

Es hängt davon ab, was Sie tun möchten. reserveist nicht fügen Sie alle Elemente der vector; es ändert nur das capacity(), was garantiert, dass das Hinzufügen von Elementen nicht neu zugewiesen wird (und z. B. Iteratoren ungültig macht). resizefügt sofort Elemente hinzu. Wenn Sie später Elemente hinzufügen möchten ( insert(), push_back()), verwenden Sie reserve. Wenn Sie später (mit []oder at()) auf Elemente zugreifen möchten , verwenden Sie resize. Du MyClass::my_methodkannst also entweder sein:

void MyClass::my_method()
{
    my_member.clear();
    my_member.reserve( n_dim );
    for ( int k = 0; k < n_dim; ++ k ) {
        my_member.push_back( k );
    }
}

oder

void MyClass::my_method()
{
    my_member.resize( n_dim );
    for ( int k = 0; k < n_dim; ++ k ) {
        my_member[k] = k;
    }
}

Welchen Sie gewählt haben, ist eine Frage des Geschmacks, aber der von Ihnen zitierte Code ist eindeutig falsch.

James Kanze
quelle
3

Es sollte wahrscheinlich eine Diskussion darüber geben, wann beide Methoden mit einer Zahl aufgerufen werden, die WENIGER als die aktuelle Größe des Vektors ist.

Anrufe reserve()mit einer Nummer, die kleiner als die Kapazität ist, haben keinen Einfluss auf die Größe oder die Kapazität.

Wenn Sie resize()mit einer Nummer anrufen, die kleiner als die aktuelle Größe ist, wird der Container auf diese Größe reduziert, wodurch die überschüssigen Elemente effektiv zerstört werden.

Zusammenfassend lässt resize()sich sagen, dass Speicher frei wird, während dies reserve()nicht der Fall ist.

Dula
quelle
Größenänderung gibt niemals Speicher frei. Wenn die Größe kleiner wird, werden Destruktoren aufgerufen, aber der Speicher bleibt erhalten (Kapazität ändert sich nicht).
John Gordon
2

Ja, Sie haben Recht, Luchian hat gerade einen Tippfehler gemacht und ist wahrscheinlich zu kaffeeentzogen, um seinen Fehler zu erkennen.

Konrad Rudolph
quelle
1

Durch Größenänderung wird tatsächlich die Anzahl der Elemente im Vektor geändert. Neue Elemente werden standardmäßig erstellt, wenn durch die Größenänderung der Vektor wächst.

vector<int> v;
v.resize(10);
auto size = v.size();

In diesem Fall beträgt die Größe 10.

Reserve hingegen fordert nur an, dass der interne Puffer auf die angegebene Größe vergrößert wird, ändert jedoch nicht die "Größe" des Arrays, sondern nur seine Puffergröße.

vector<int> v;
v.reserve(10);
auto size = v.size();

In diesem Fall ist die Größe immer noch 0.

Um Ihre Frage zu beantworten: Ja, Sie haben Recht, auch wenn Sie genügend Speicherplatz reservieren, greifen Sie mit dem Indexoperator immer noch auf nicht initialisierten Speicher zu. Mit einem int, das nicht so schlecht ist, aber im Fall eines Klassenvektors würden Sie auf Objekte zugreifen, die nicht konstruiert wurden.

Die Überprüfung der Grenzen von Compilern, die auf den Debug-Modus eingestellt sind, kann offensichtlich durch dieses Verhalten verwechselt werden, weshalb möglicherweise der Absturz auftritt.

odinthenerd
quelle