Czy elementy std :: vector są gwarantowane jako ciągłe?


111

Moje pytanie jest proste: czy elementy std :: vector są na pewno ciągłe? W słowie kolejności, czy mogę użyć wskaźnika do pierwszego elementu std :: vector jako tablicy C?

Jeśli moja pamięć dobrze mi służy, standard C ++ nie dawał takiej gwarancji. Jednak wymagania std :: vector były takie, że praktycznie niemożliwe było ich spełnienie, gdyby elementy nie były ciągłe.

Czy ktoś może to wyjaśnić?

Przykład:

std::vector<int> values;
// ... fill up values

if( !values.empty() )
{
    int *array = &values[0];
    for( int i = 0; i < values.size(); ++i )
    {
        int v = array[i];
        // do something with 'v'
    }
}

Wiem, że masz kłopoty, jeśli zmutujesz valuesw tym ifbloku. Nie znam jednak odpowiedzi na twoje pytanie, więc zostawiam tylko komentarz. :)
Greg D

@Greg: Jaki problem - czy możesz to trochę rozwinąć?
Reunanen

Przypuszczam, że miał na myśli, że wypychanie nowych wartości może wywołać „realokację”, która spowodowałaby unieważnienie tablicy.
Martin Cote

Wywołania, które ulegają mutacji values, a konkretnie zmieniają jego rozmiar (np. push_back()), Mogą skłonić do ponownej alokacji wektora bazowego, który unieważnia wskaźnik skopiowany do array. Ta sama zasada obowiązuje przy używaniu vector :: iterator zamiast wskaźnika do wektora. :)
Greg D

1
Tak, umieściłem `` wokół wartości '', aby wyjaśnić, że mówię o samej klasie, a nie o wartościach w niej zawartych. :) Niefortunne nazewnictwo i tak dalej. Nie sądzę, żeby to naprawdę był problem w ogólnym przypadku, w którym to pytanie jest istotne - dlaczego ktoś miałby złapać wskaźnik do pamięci, a następnie zacząć grzebać w wektorze zamiast używać wskaźnika? Głupota.
Greg D

Odpowiedzi:


118

Zostało to pominięte w normie C ++ 98, ale później dodane jako część TR. Nadchodzący standard C ++ 0x będzie oczywiście zawierał to wymaganie.

Od n2798 (wersja robocza C ++ 0x):

23.2.6 Wektor szablonu klasy [wektor]

1 Wektor jest kontenerem sekwencji obsługującym iteratory dostępu swobodnego. Ponadto obsługuje (amortyzowane) operacje wstawiania i kasowania o stałym czasie na końcu; wstawianie i kasowanie w środku zajmuje liniowy czas. Zarządzanie pamięcią masową jest obsługiwane automatycznie, chociaż można podać wskazówki dotyczące poprawy wydajności. Elementy wektora są przechowywane w sposób ciągły, co oznacza, że ​​jeśli v jest wektorem, w którym T jest innym typem niż bool, to jest zgodny z tożsamością & v [n] == & v [0] + n dla wszystkich 0 <= n <v .rozmiar().


3
Jest to również określone w ISO 14882, wydanie 2: Sekcja 23.2.4 [lib.vector]: „Elementy wektora są przechowywane w sposób ciągły, co oznacza, że ​​jeśli v jest wektorem <T, Allocator>, gdzie T jest innym typem niż bool, to przestrzega tożsamości & v [n] == & v [0] + n dla wszystkich 0 <= n <v.size (). "
Mike Caron

4
więc s, TR, TC, :) Właściwie C ++ 03 jest również nazywany C ++ 98-TC1 (sprostowanie techniczne) z tego, co przeczytałem
Johannes Schaub - litb

2
A co z wektorami wektorów? Wektory wewnętrzne są zaraz po wektorach wewnętrznych ostatniej grupy?
huseyin tugrul buyukisik

1
@huseyin tugrul buyukisik czy znasz odpowiedź na to pytanie? Zastanawiam się też, jak to działa
David Doria

1
@huseyin tugrul buyukisik To oczywiście prawda, ale to kolejne wystąpienia std::vectorsą ciągłe. Np .: w std::vector<std::vector<int>> velementach v[0], v[1]... następnie są przechowywane w pamięci, ale element v[0].back()i v[1].front()nie są gwarancją.
jarzec

27

Jak wskazywały inne odpowiedzi, zawartość wektora jest gwarantowana jako ciągła (z wyjątkiem dziwności boola).

Komentarz, który chciałem dodać, jest taki, że jeśli wykonasz wstawienie lub usunięcie w wektorze, co może spowodować ponowne przydzielenie wektora jego pamięci, spowoduje to unieważnienie wszystkich zapisanych wskaźników i iteratorów.


1
Elementy nadal byłyby przechowywane w ciągłym bloku pamięci, po prostu znajdowałyby się w innym miejscu. Pytanie dotyczyło w szczególności przyległości.
Dima

2
Ale istniejące wskaźniki i iteratory zostałyby unieważnione.
Bill Lynch

Słuszna uwaga. Powinieneś umieścić to w swojej odpowiedzi, aby wyjaśnić, co masz na myśli.
Dima

najbardziej użyteczna odpowiedź do mnie
CoffeDeveloper

Teraz już wiem, dlaczego mój program wczoraj segfaultował, kiedy przeszedłem przez niego w podwójnej pętli usuwając pewne elementy :) Dzięki!
user2891462

9

W rzeczywistości standard gwarantuje, że a vectorjest ciągły w pamięci i &a[0]może zostać przekazany do Cfunkcji, która oczekuje tablicy.

Wyjątkiem od tej reguły jest to, vector<bool>że używa tylko jednego bitu na, boolwięc chociaż ma pamięć ciągłą, nie może być używany jako bool*(jest to powszechnie uważane za fałszywą optymalizację i błąd).

BTW, dlaczego nie używasz iteratorów? Po to są.


1
> BTW, dlaczego nie używasz iteratorów? Po to są. Może przeczytał nowy artykuł Alexanrescu na ten temat: boostcon.com/site-media/var/sphene/sphwiki/attachment/2009/05/…
Nemanja Trifunovic

Dzięki za link, przejdę do mojej listy lektur (staram się nie przegapić artykułów Alexandresu)
Motti

Mwahaha, wydaje się, że w dzisiejszych czasach wszyscy mówią o tej prezentacji. Słuchaj, dyskusja jest wciąż gorąca: groups.google.com/group/comp.lang.c++.moderated/browse_thread/…
Johannes Schaub - litb

Jeśli przeczytasz uważnie, artykuł Alexandrescu tak naprawdę nie mówi „Nie używaj iteratorów w C ++”, ale mówi „Sprawdź D”. Podejście, które opisuje w tym artykule, jest uderzająco podobne do wszelkich istniejących języków i frameworków, które wchłonęły dziedzictwo funkcjonalne (List, Scheme, Haskell) i poważnie wątpię, czy kolejna składnia oparta na C jest idealnym punktem wyjścia do lepszego obsługa listy. Jakiś czas w zeszłym roku przez chwilę próbowałem go przekonać, aby zamiast tego skierował swoje spore talenty na udoskonalenie już uznanego języka, takiego jak C #, ale nie obawiam się, że odniesie sukces! :)
Daniel Earwicker

6

Jak już powiedzieli inni, vectorwewnętrznie używa ciągłej tablicy obiektów. Wskaźniki do tej tablicy powinny być traktowane jako nieprawidłowe, jeśli jakakolwiek funkcja składowa niebędąca stałą jest nazywana IIRC.

Jest jednak wyjątek !!

vector<bool>ma wyspecjalizowaną implementację zaprojektowaną w celu zaoszczędzenia miejsca, dzięki czemu każdy bool używa tylko jednego bitu. Podstawowa tablica nie jest ciągłą tablicą bool, a arytmetyka tablicy vector<bool>nie działa tak vector<T>, jak powinna.

(Przypuszczam, że jest to również możliwe, że może to dotyczyć dowolnej specjalizacji wektora, ponieważ zawsze możemy zaimplementować nową. std::vector<bool>Jest to jednak jedyna, błąd, standardowa specjalizacja, na której prosta arytmetyka wskaźnikowa nie będzie działać.)


Użytkownik nie może się specjalizować std::vector, a wszystkie inne wektory muszą korzystać z ciągłego magazynu. Dlatego std::vector<bool>jest (na szczęście) jedynym standardowym wektorem, który jest dziwny. (Jestem przekonany, że ta specjalizacja powinna zostać wycofana i zastąpiona np. A std::dynamic_bitseto podobnej funkcjonalności. To nie jest zła struktura danych, to po prostu nie jest wektor.)
Arne Vogel

3

Znalazłem ten wątek, ponieważ mam przypadek użycia, w którym wektory korzystające z pamięci ciągłej są zaletą.

Uczę się używać obiektów bufora wierzchołków w OpenGL. Utworzyłem klasę opakowującą zawierającą logikę bufora, więc wszystko, co muszę zrobić, to przekazać tablicę wartości zmiennoprzecinkowych i kilka wartości konfiguracyjnych, aby utworzyć bufor. Chcę mieć możliwość wygenerowania buforu z funkcji na podstawie danych wejściowych użytkownika, więc długość nie jest znana w czasie kompilacji. Zrobienie czegoś takiego byłoby najłatwiejszym rozwiązaniem:

void generate(std::vector<float> v)
{
  float f = generate_next_float();
  v.push_back(f);
}

Teraz mogę przekazać elementy pływające wektora jako tablicę do funkcji związanych z buforem OpenGL. Eliminuje to również konieczność określania długości tablicy przez sizeof.

Jest to o wiele lepsze niż przydzielanie ogromnej tablicy do przechowywania pływaków i nadzieja, że ​​zrobiłem ją wystarczająco dużą, lub tworzenie własnej dynamicznej tablicy z ciągłą pamięcią.


2
ta funkcja nie ma dla mnie żadnego sensu. czy masz na myśli przekazywanie odniesienia lub wskaźnika do siebie, va nie do vsiebie? ponieważ vsamo przejście spowoduje wykonanie kopii wewnątrz funkcji, która przestanie istnieć po zakończeniu funkcji. Tak więc wpychasz coś na wektor tylko po to, aby usunąć wektor po zakończeniu funkcji.
johnbakers

1

cplusplus.com:

Kontenery wektorowe są implementowane jako tablice dynamiczne; Podobnie jak zwykłe tablice, kontenery wektorowe mają swoje elementy przechowywane w ciągłych lokalizacjach pamięci, co oznacza, że ​​dostęp do ich elementów można uzyskać nie tylko za pomocą iteratorów, ale także za pomocą przesunięć na regularnych wskaźnikach do elementów.


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.