std :: vector :: resize () a std :: vector :: Reserve ()

80

Jest wątek w sekcji komentarzy w tym poście na temat korzystania std::vector::reserve()Vs. std::vector::resize().

Oto oryginalny kod:

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

Uważam, że aby wpisać elementy w elemencie vector, właściwą rzeczą jest wywołanie std::vector::resize(), a nie std::vector::reserve().

W rzeczywistości następujący kod testowy „ulega awarii” w kompilacjach debugowania w VS2010 z dodatkiem SP1:

#include <vector>

using namespace std;

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

    return 0;
}

Mam rację, czy się mylę? I czy VS2010 SP1 jest poprawny, czy nie?

Panie C64
źródło
12
Wyjaśnienie może być tak proste, jak „Myliłem się”: D
Luchian Grigore,
7
Oznaczyłem to jako „zbyt zlokalizowane”, ponieważ @LuchianGrigore rzadko się myli
domyślnie
1
@Default czytaj „rzadko źle” jako „szybko poprawiając swoje błędy” :)
Luchian Grigore
1
Kod w oryginalnym poście został zaktualizowany, aby prawidłowo używać resize(), a wątpliwości zostały wyjaśnione. Do moderatorów: możesz usunąć to pytanie, jeśli jest „zbyt zlokalizowane”, lub zachować je, jeśli uważasz, że może pomóc komuś w przyszłości.
Pan C64
1
to pytanie faktycznie rozwiewa moje wątpliwości, kiedy migruję mój projekt z vc6 do vs2013 .. dzięki :))
pengMiao

Odpowiedzi:

114

Istnieją dwie różne metody z jakiegoś powodu:

std::vector::reserve przydzieli pamięć, ale nie zmieni rozmiaru wektora, który będzie miał logiczny rozmiar taki sam jak wcześniej.

std::vector::resizefaktycznie zmodyfikuje rozmiar twojego wektora i wypełni dowolną przestrzeń obiektami w ich domyślnym stanie. Jeśli są to liczby int, wszystkie będą miały wartość zero.

Po dokonaniu rezerwacji, w twoim przypadku będziesz potrzebować dużo push_backs, aby napisać do elementu 5. Jeśli nie chcesz tego robić, w swoim przypadku powinieneś użyć resize.

Jedna rzecz dotycząca rezerwy: jeśli następnie dodasz elementy za pomocą push_back, dopóki nie osiągniesz zarezerwowanej pojemności, wszelkie istniejące odwołania, iteratory lub wskaźniki do danych w twoim wektorze pozostaną ważne. Więc jeśli zarezerwuję 1000, a mój rozmiar to 5, &vec[4]pozostanie taki sam, dopóki wektor nie będzie miał 1000 elementów. Potem mogę zadzwonić push_back()i zadziała, ale wcześniej zapisany wskaźnik &vec[4]może już nie być ważny.

Dojną krową
źródło
więc dla pustego wektora, czyli vec, po rezerwie vec [1] zakończy się błędem segmentu.
hailinzeng
2
vec [1] byłoby niezdefiniowanym zachowaniem.
CashCow
Będzie std::vector::reservezapobiec sporadyczne kopiowanie pełnej tablicy na push_back?
Post Self
Czy to tylko dla C ++ 11 czy konkretnej implementacji standardowej? Wygląda na to, że kod z rezerwacją i dostępem z [] działa dobrze? godbolt.org/z/MhgFdZ
Steve_Corrin
1
@Steve_Corrin Niezdefiniowane zachowanie jest niezdefiniowane. Może się wydawać, że działa. To wciąż nieprawidłowy kod. Indeksowanie elementów, które nie istnieją w < size()kontenerze, jest niedozwolone. Z definicji języka ich tam nie ma. Jeśli twój kompilator zdecyduje się nie uruchamiać nukes i zamiast tego po prostu przegląda pamięć RAM w sposób, w jaki chcesz, to po prostu powodzenia. Albo chyba pecha; Idealnie byłoby, gdybyśmy wyłapali wszystkie nieprawidłowe rzeczy, które zrobiliby wszyscy programiści, ale powodzenia !
underscore_d
25

Odpowiedział tutaj Jan Hudec : Wybór między vector :: resize () a vector :: Reserve ()

Te dwie funkcje robią bardzo różne rzeczy.

Metoda resize () (i przekazanie argumentu do konstruktora jest równoznaczne z tym) wstawi do wektora podaną liczbę elementów (ma opcjonalny drugi argument do określenia ich wartości). Wpłynie to na size (), iteracja przejdzie przez wszystkie te elementy, push_back wstawi po nich i możesz uzyskać do nich bezpośredni dostęp za pomocą operatora [].

Metoda Reserve () przydziela tylko pamięć, ale pozostawia ją niezainicjowaną. Wpływa tylko na pojemność (), ale size () pozostanie bez zmian. Nie ma wartości dla obiektów, ponieważ nic nie jest dodawane do wektora. Jeśli następnie wstawisz elementy, ponowna alokacja nie nastąpi, ponieważ została wykonana z góry, ale to jedyny efekt.

Więc to zależy od tego, czego chcesz. Jeśli chcesz mieć tablicę zawierającą 1000 domyślnych elementów, użyj resize (). Jeśli chcesz mieć tablicę, do której spodziewasz się wstawić 1000 elementów i chcesz uniknąć kilku alokacji, użyj funkcji Reserve ().

EDYCJA: Komentarz Blastfurnace sprawił, że ponownie przeczytałem pytanie i zdałem sobie sprawę, że w twoim przypadku prawidłowa odpowiedź to nie przydzielaj wstępnie ręcznie. Po prostu wkładaj elementy na końcu, jak potrzebujesz. Wektor automatycznie zmieni przydział w razie potrzeby i zrobi to wydajniej niż wspomniany sposób ręczny. Jedynym przypadkiem, w którym funkcja Reserve () ma sens, jest sytuacja, w której masz dość dokładne oszacowanie całkowitego rozmiaru, którego potrzebujesz, łatwo dostępne z wyprzedzeniem.

EDIT2: Edycja pytania reklamowego: Jeśli masz wstępne oszacowanie, zarezerwuj () to oszacowanie i jeśli okaże się to niewystarczające, po prostu pozwól wektorowi zrobić to.

lucasg
źródło
12

To zależy od tego, co chcesz robić. reservema nie dodawać żadnych elementów do vector; zmienia tylko capacity(), co gwarantuje, że dodawanie elementów nie spowoduje realokacji (np. unieważni iteratory). resizedodaje elementy natychmiast. Jeśli chcesz później dodać elementy ( insert(), push_back()), użyj reserve. Jeśli chcesz później uzyskać dostęp do elementów (używając []lub at()), użyj resize. MyClass::my_methodMożesz więc być:

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 );
    }
}

lub

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

Który z nich wybrałeś to kwestia gustu, ale cytowany kod jest wyraźnie nieprawidłowy.

James Kanze
źródło
3

Prawdopodobnie powinna być dyskusja na temat tego, kiedy obie metody są wywoływane z liczbą, która jest MNIEJSZA niż bieżący rozmiar wektora.

Połączenie reserve()z numerem mniejszym niż pojemność nie wpłynie na rozmiar ani pojemność.

Wywołanie resize()z numerem mniejszym niż obecny rozmiar kontenera zostanie zmniejszony do tego rozmiaru, skutecznie niszcząc nadmiar elementów.

Podsumowując resize(), zwolni pamięć reserve(), ale nie.

Dula
źródło
Zmiana rozmiaru nigdy nie zwalnia pamięci. Gdy rozmiar będzie się zmniejszał, zostaną wywołane mniejsze destruktory, ale pamięć zostanie zachowana (pojemność się nie zmieni).
John Gordon
2

Tak, masz rację, Luchian właśnie popełnił literówkę i prawdopodobnie jest zbyt pozbawiony kawy, aby zdać sobie sprawę ze swojego błędu.

Konrad Rudolph
źródło
1

zmiana rozmiaru faktycznie zmienia liczbę elementów w wektorze, nowe elementy są domyślnie konstruowane, jeśli zmiana rozmiaru powoduje wzrost wektora.

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

w tym przypadku rozmiar to 10.

rezerwa z drugiej strony tylko żąda powiększenia bufora wewnętrznego do określonego rozmiaru, ale nie zmienia „rozmiaru” tablicy, zmienia się jedynie rozmiar jej bufora.

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

w tym przypadku rozmiar nadal wynosi 0.

Odpowiadając na pytanie, tak, masz rację, nawet jeśli zarezerwujesz wystarczającą ilość miejsca, nadal uzyskujesz dostęp do niezainicjowanej pamięci za pomocą operatora indeksu. Z wartością int nie jest tak źle, ale w przypadku wektora klas miałbyś dostęp do obiektów, które nie zostały skonstruowane.

Sprawdzanie granic kompilatorów ustawionych na tryb debugowania może być oczywiście zdezorientowane przez to zachowanie, co może być przyczyną awarii.

odinthenerd
źródło