Jak skopiować zawartość tablicy do std :: vector w C ++ bez zapętlania?

122

Mam tablicę wartości, które są przekazywane do mojej funkcji z innej części programu, którą muszę przechowywać do późniejszego przetworzenia. Ponieważ nie wiem, ile razy moja funkcja zostanie wywołana, zanim nadejdzie czas na przetworzenie danych, potrzebuję dynamicznej struktury pamięci, więc wybrałem plik std::vector. Nie chcę wykonywać standardowej pętli dla push_backwszystkich wartości z osobna, byłoby miło, gdybym mógł to wszystko skopiować za pomocą czegoś podobnego do memcpy.

bsruth
źródło

Odpowiedzi:

117

Jeśli możesz skonstruować wektor po uzyskaniu tablicy i rozmiaru tablicy, możesz po prostu powiedzieć:

std::vector<ValueType> vec(a, a + n);

... zakładając, że ajest to twoja tablica i nliczba zawartych w niej elementów. W przeciwnym razie std::copy()w /resize() zrobi to.

Trzymałbym się z dala od memcpy() chyba że masz pewność, że wartości są zwykłymi starymi typami danych (POD).

Warto również zauważyć, że żadna z nich tak naprawdę nie omija pętli for - to tylko kwestia tego, czy musisz to zobaczyć w swoim kodzie, czy nie. Podczas kopiowania wartości nie można uniknąć wydajności środowiska wykonawczego O (n).

Na koniec zwróć uwagę, że tablice w stylu C są całkowicie prawidłowymi kontenerami dla większości algorytmów STL - surowy wskaźnik jest równoważny begin(), a ( ptr + n) jest równoważny end().

Drew Hall
źródło
4
Powodem, dla którego pętle i wywoływanie push_back są złe, jest to, że możesz wymusić wielokrotną zmianę rozmiaru wektora, jeśli tablica jest wystarczająco długa.
bradtgmurray
@bradtgmurray: Myślę, że każda rozsądna implementacja konstruktora wektora "dwóch iteratorów", którą zasugerowałem powyżej, wywołałaby najpierw std :: distance () na dwóch iteratorach, aby uzyskać wymaganą liczbę elementów, a następnie przydzieliłaby tylko raz.
Drew Hall
4
@bradtgmurray: Nawet funkcja push_back () nie byłaby taka zła ze względu na wykładniczy wzrost wektorów (inaczej „zamortyzowany stały czas”). Wydaje mi się, że w najgorszym przypadku czas pracy byłby tylko 2x gorszy.
Drew Hall
2
A jeśli wektor już tam jest, a vec.clear (); vec.insert (vec.begin (), a, a + n); zadziała również. Wtedy nie wymagałbyś nawet, aby a był wskaźnikiem, tylko iteratorem, a przypisanie wektora byłoby ogólne niepowodzenie (i sposób C ++ / STL).
MP24
6
Inną alternatywą, gdy stanie skonstruować byłoby przypisać : vec.assign(a, a+n), który byłby bardziej kompaktowy niż Kopiuj i rozmiaru.
mMontu
209

Było tu wiele odpowiedzi i prawie wszystkie z nich wykonają swoją pracę.

Jest jednak kilka mylących rad!

Oto opcje:

vector<int> dataVec;

int dataArray[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
unsigned dataArraySize = sizeof(dataArray) / sizeof(int);

// Method 1: Copy the array to the vector using back_inserter.
{
    copy(&dataArray[0], &dataArray[dataArraySize], back_inserter(dataVec));
}

// Method 2: Same as 1 but pre-extend the vector by the size of the array using reserve
{
    dataVec.reserve(dataVec.size() + dataArraySize);
    copy(&dataArray[0], &dataArray[dataArraySize], back_inserter(dataVec));
}

// Method 3: Memcpy
{
    dataVec.resize(dataVec.size() + dataArraySize);
    memcpy(&dataVec[dataVec.size() - dataArraySize], &dataArray[0], dataArraySize * sizeof(int));
}

// Method 4: vector::insert
{
    dataVec.insert(dataVec.end(), &dataArray[0], &dataArray[dataArraySize]);
}

// Method 5: vector + vector
{
    vector<int> dataVec2(&dataArray[0], &dataArray[dataArraySize]);
    dataVec.insert(dataVec.end(), dataVec2.begin(), dataVec2.end());
}

Krótko mówiąc, metoda 4, wykorzystująca vector :: insert, jest najlepsza w scenariuszu bsruth.

Oto kilka krwawych szczegółów:

Metoda 1 jest prawdopodobnie najłatwiejsza do zrozumienia. Po prostu skopiuj każdy element z tablicy i wepchnij go z tyłu wektora. Niestety, jest powolny. Ponieważ istnieje pętla (implikowana z funkcją kopiowania), każdy element musi być traktowany indywidualnie; nie można poprawić wydajności na podstawie faktu, że wiemy, że tablica i wektory są ciągłymi blokami.

Metoda 2 to sugerowana poprawa wydajności w stosunku do Metody 1; po prostu zarezerwuj rozmiar tablicy przed jej dodaniem. W przypadku dużych tablic może to pomóc. Jednak najlepszą radą tutaj jest, aby nigdy nie używać rezerwy, chyba że profilowanie sugeruje, że możesz być w stanie uzyskać poprawę (lub musisz upewnić się, że twoje iteratory nie zostaną unieważnione). Bjarne zgadza się . Nawiasem mówiąc, zauważyłem, że ta metoda działała najwolniej przez większość czasu, chociaż staram się kompleksowo wyjaśnić, dlaczego regularnie była znacznie wolniejsza niż metoda 1 ...

Metoda 3 to stara szkoła - rzuć trochę C na problem! Działa dobrze i szybko w przypadku typów POD. W tym przypadku konieczne jest wywołanie resize, ponieważ memcpy działa poza granicami wektora i nie ma sposobu, aby powiedzieć wektorowi, że jego rozmiar się zmienił. Oprócz tego, że jest to brzydkie rozwiązanie (kopiowanie bajtów!), Pamiętaj, że może to być używane tylko dla typów POD . Nigdy bym nie skorzystał z tego rozwiązania.

Metoda 4 to najlepszy sposób. To znaczy jest jasne, jest (zwykle) najszybsze i działa na dowolnych obiektach. Nie ma wad korzystania z tej metody w tej aplikacji.

Metoda 5 to ulepszenie metody 4 - skopiuj tablicę do wektora, a następnie dołącz ją. Dobra opcja - ogólnie szybka i przejrzysta.

Wreszcie wiesz, że możesz używać wektorów zamiast tablic, prawda? Nawet jeśli funkcja oczekuje tablic w stylu c, możesz użyć wektorów:

vector<char> v(50); // Ensure there's enough space
strcpy(&v[0], "prefer vectors to c arrays");

Mam nadzieję, że to pomoże komuś tam!

MattyT
źródło
6
Nie możesz bezpiecznie i przenośnie odwoływać się do „& dataArray [dataArraySize]” - jest to wyłuskiwanie wskaźnika / iteratora poza końcem. Zamiast tego możesz powiedzieć dataArray + dataArraySize, aby uzyskać wskaźnik bez konieczności uprzedniego wyłuskiwania go.
Drew Hall
2
@Drew: tak, możesz, przynajmniej w C. Jest zdefiniowane, że &exprnie ocenia expr, a jedynie oblicza jego adres. Wskaźnik znajdujący się jeden za ostatnim elementem jest również całkowicie poprawny.
Roland Illig
2
Czy próbowałeś wykonać metodę 4 z 2? tj. rezerwowanie miejsca przed włożeniem. Wydaje się, że jeśli rozmiar danych jest duży, wiele wstawień będzie wymagało wielu ponownych alokacji. Ponieważ znamy rozmiar a priori, możemy dokonać realokacji przed wstawieniem.
Jorge Leitao
2
@MattyT jaki jest sens metody 5? Po co tworzyć pośrednią kopię danych?
Rusłan
2
Osobiście wolałbym raczej czerpać korzyści z automatycznego rozpadu tablic na wskaźniki: dataVec.insert(dataVec.end(), dataArray, dataArray + dataArraySize);- wydaje mi się dużo jaśniejszy. Nie można też niczego uzyskać z metody 5, tylko wygląda dość nieefektywnie - chyba że kompilator jest w stanie ponownie zoptymalizować wektor.
Aconcagua
37

Jeśli wszystko, co robisz, to zastępowanie istniejących danych, możesz to zrobić

std::vector<int> data; // evil global :)

void CopyData(int *newData, size_t count)
{
   data.assign(newData, newData + count);
}
Torlack
źródło
1
Proste do zrozumienia i zdecydowanie najszybsze rozwiązanie (to tylko memcpy za kulisami).
Don Scott
Czy deta. assign jest szybsza niż data.insert?
Jim
11

std :: copy jest tym, czego szukasz.

Łukasz
źródło
10

Ponieważ mogę edytować tylko własną odpowiedź, zamierzam utworzyć złożoną odpowiedź na podstawie innych odpowiedzi na moje pytanie. Dziękuję wszystkim, którzy odpowiedzieli.

Używając std :: copy , to nadal iteruje w tle, ale nie musisz wpisywać kodu.

int foo(int* data, int size)
{
   static std::vector<int> my_data; //normally a class variable
   std::copy(data, data + size, std::back_inserter(my_data));
   return 0;
}

Używanie zwykłego memcpy . Jest to prawdopodobnie najlepiej używane dla podstawowych typów danych (tj. Int), ale nie dla bardziej złożonych tablic struktur lub klas.

vector<int> x(size);
memcpy(&x[0], source, size*sizeof(int));
bsruth
źródło
Miałem zamiar polecić to podejście.
mmocny
Najprawdopodobniej bardziej efektywna jest zmiana rozmiaru wektora z góry, jeśli znasz rozmiar z wyprzedzeniem i nie używasz back_inserter.
Łukasz
możesz dodać my_data.reserve (rozmiar)
David Nehme
Zwróć uwagę, że wewnętrznie robi to dokładnie to, czego chcesz uniknąć. Nie kopiuje bitów, po prostu zapętla i wywołuje push_back (). Chyba chciałeś tylko uniknąć wpisywania kodu?
mmocny
1
Wjy nie używa konstruktora wektorowego do kopiowania danych?
Martin York
3

unikaj mempy, mówię. Nie ma powodu, by bawić się operacjami wskaźnika, chyba że naprawdę musisz. Ponadto będzie działać tylko dla typów POD (takich jak int), ale zawiedzie, jeśli masz do czynienia z typami, które wymagają konstrukcji.

Assaf Lavie
źródło
8
Może to powinien być komentarz do jednej z pozostałych odpowiedzi, ponieważ tak naprawdę nie proponujesz rozwiązania.
finnw
3
int dataArray[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//source

unsigned dataArraySize = sizeof(dataArray) / sizeof(int);

std::vector<int> myvector (dataArraySize );//target

std::copy ( myints, myints+dataArraySize , myvector.begin() );

//myvector now has 1,2,3,...10 :-)
Antonio Ramasco
źródło
2
Choć ten fragment kodu jest mile widziany i może zapewnić jakąś pomoc, byłoby znacznie się poprawiła, gdyby obejmowała wyjaśnienie z how i dlaczego to rozwiązuje problem. Pamiętaj, że odpowiadasz na pytanie do czytelników w przyszłości, a nie tylko osoba, która zapyta teraz! Proszę edytować swoje odpowiedzi, aby dodać wyjaśnienie, i dać wskazówkę co zastosować ograniczenia i założenia.
Toby Speight
4
Czekaj, co jest myints?
mavavilj
2

Jeszcze inna odpowiedź, ponieważ osoba ta powiedziała „Nie wiem, ile razy moja funkcja zostanie wywołana”, możesz użyć metody wstawiania wektorów w taki sposób, aby dołączyć tablice wartości na końcu wektora:

vector<int> x;

void AddValues(int* values, size_t size)
{
   x.insert(x.end(), values, values+size);
}

Podoba mi się ten sposób, ponieważ implementacja wektora powinna być w stanie zoptymalizować pod kątem najlepszego sposobu wstawiania wartości na podstawie typu iteratora i samego typu. W pewnym stopniu odpowiadasz na temat implementacji STL.

Jeśli chcesz zagwarantować największą prędkość i wiesz, że Twój typ to POD, to polecam metodę zmiany rozmiaru w odpowiedzi Thomasa:

vector<int> x;

void AddValues(int* values, size_t size)
{
   size_t old_size(x.size());
   x.resize(old_size + size, 0);
   memcpy(&x[old_size], values, size * sizeof(int));
}
Shane Powell
źródło
1

Oprócz metod przedstawionych powyżej, musisz upewnić się, że używasz std :: Vector.reserve (), std :: Vector.resize () lub skonstruujesz wektor do rozmiaru, aby upewnić się, że wektor ma wystarczającą liczbę elementów w do przechowywania danych. jeśli nie, zepsujesz pamięć. Dotyczy to zarówno std :: copy (), jak i memcpy ().

To jest powód używania vector.push_back (), nie możesz pisać poza końcem wektora.

Thomas Jones-Low
źródło
Jeśli używasz back_insertera, nie musisz wstępnie rezerwować rozmiaru wektora, do którego kopiujesz. back_inserter wykonuje push_back ().
John Dibling
0

Zakładając, że wiesz, jak duży jest element w wektorze:

std::vector<int> myArray;
myArray.resize (item_count, 0);
memcpy (&myArray.front(), source, item_count * sizeof(int));

http://www.cppreference.com/wiki/stl/vector/start

Thomas Jones-Low
źródło
Czy to nie zależy od implementacji std :: vector?
ReaperUnreal
To okropne! Wypełniasz tablicę dwukrotnie, jedną 0, a następnie odpowiednimi wartościami. Po prostu zrób: std :: vector <int> myArray (source, source + item_count); i zaufaj swojemu kompilatorowi, że stworzy memcpy!
Chris Jefferson,
Zaufaj kompilatorowi, który wyprodukuje __memcpy_int_aligned; to powinno być jeszcze szybsze
MSalters