Korzystam z zewnętrznej biblioteki, która w pewnym momencie daje mi surowy wskaźnik do tablicy liczb całkowitych i rozmiaru.
Teraz chciałbym użyć, std::vector
aby uzyskać dostęp i zmodyfikować te wartości w miejscu, zamiast uzyskiwać do nich dostęp za pomocą surowych wskaźników.
Oto sztuczny przykład, który wyjaśnia tę kwestię:
size_t size = 0;
int * data = get_data_from_library(size); // raw data from library {5,3,2,1,4}, size gets filled in
std::vector<int> v = ????; // pseudo vector to be used to access the raw data
std::sort(v.begin(), v.end()); // sort raw data in place
for (int i = 0; i < 5; i++)
{
std::cout << data[i] << "\n"; // display sorted raw data
}
Oczekiwany wynik:
1
2
3
4
5
Powodem jest to, że muszę zastosować algorytmy z <algorithm>
(sortowania, zamiany elementów itp.) Na tych danych.
Z drugiej strony zmiany rozmiaru tego wektora nigdy nie zostanie zmieniona, tak push_back
, erase
, insert
nie są wymagane do pracy na tym wektorze.
Mógłbym zbudować wektor na podstawie danych z biblioteki, użyć zmodyfikować ten wektor i skopiować dane z powrotem do biblioteki, ale byłyby to dwie kompletne kopie, których chciałbym uniknąć, ponieważ zestaw danych mógłby być naprawdę duży.
std::vector_view
, prawda?std::vector
działa.sort(arrayPointer, arrayPointer + elementCount);
.Odpowiedzi:
Problem polega na tym, że
std::vector
trzeba wykonać kopię elementów z tablicy, którą inicjujesz, ponieważ ma ona własność zawartych w niej obiektów.Aby tego uniknąć, możesz użyć obiektu wycinka dla tablicy (tj. Podobnie do tego, co
std::string_view
jeststd::string
). Możesz napisać własnąarray_view
implementację szablonu klasy, której instancje są konstruowane przez przeniesienie surowego wskaźnika do pierwszego elementu tablicy i jej długości:array_view
nie przechowuje tablicy; po prostu trzyma wskaźnik na początku tablicy i jej długość. Dlategoarray_view
obiekty są tanie w budowie i kopiowaniu.Ponieważ
array_view
zapewniabegin()
iend()
członków funkcje, można użyć standardowych algorytmów Library (npstd::sort
,std::find
,std::lower_bound
, itd.) Na jej temat:Wynik:
Zamiast tego użyj
std::span
(lubgsl::span
)Powyższa implementacja ujawnia koncepcję obiektów wycinanych . Jednak od C ++ 20 można bezpośrednio użyć
std::span
zamiast tego. W każdym razie możesz używaćgsl::span
od C ++ 14.źródło
C ++ 20
std::span
Jeśli potrafisz używać C ++ 20, możesz użyć
std::span
pary wskaźników i długości, która daje użytkownikowi widok na ciągłą sekwencję elementów. Jest jakiś swego rodzajustd::string_view
, a jednocześnie obastd::span
istd::string_view
są non-posiadania poglądów,std::string_view
to widok tylko do odczytu.Z dokumentów:
Więc działałyby następujące rzeczy:
Sprawdź to na żywo
Ponieważ
std::span
jest to zasadniczo para wskaźnik-długość, możesz również użyć w następujący sposób:Uwaga: Nie wszystkie kompilatory obsługują
std::span
. Sprawdź obsługę kompilatora tutaj .AKTUALIZACJA
Jeśli nie możesz używać C ++ 20, możesz użyć,
gsl::span
która jest w zasadzie podstawową wersją standardu C ++std::span
.Rozwiązanie C ++ 11
Jeśli jesteś ograniczony standardem C ++ 11, możesz spróbować wdrożyć własną prostą
span
klasę:Sprawdź wersję C ++ 11 na żywo
źródło
gsl::span
dla C ++ 14 i wyższych, jeśli twój kompilator nie implementujestd::span
Ponieważ biblioteka algorytmów współpracuje z iteratorami, możesz zachować tablicę.
Dla wskaźników i znanej długości tablicy
Tutaj możesz używać surowych wskaźników jako iteratorów. Obsługują wszystkie operacje obsługiwane przez iterator (przyrost, porównanie dla równości, wartość itp.):
data
wskazuje na pierwszy element tablicy jak iterator zwracany przezbegin()
idata + size
wskazuje na element za ostatnim elementem tablicy jak iterator zwracany przezend()
.Do tablic
Tutaj możesz użyć
std::begin()
istd::end()
Należy jednak pamiętać, że działa to tylko wtedy, gdy
data
nie rozpada się na wskaźnik, ponieważ wtedy brakuje informacji o długości.źródło
Możesz pobrać iteratory na surowe tablice i używać ich w algorytmach:
Jeśli pracujesz z surowymi wskaźnikami (ptr + rozmiar), możesz użyć następującej techniki:
UPD: Jednak powyższy przykład ma zły projekt. Biblioteka zwraca nam surowy wskaźnik i nie wiemy, gdzie przydzielony jest bufor podstawowy i kto powinien go zwolnić.
Zwykle program wywołujący udostępnia bufor dla funkcji wypełniania danych. W takim przypadku możemy wstępnie przydzielić wektor i użyć jego bufora:
Używając C ++ 11 lub nowszego, możemy nawet zrobić get_data_from_library (), aby zwrócić wektor. Dzięki operacjom przenoszenia nie będzie kopii pamięci.
źródło
auto begin = data;
auto end = data + size;
get_data_from_library()
przydzielane są dane zwracane przez ? Może wcale nie powinniśmy tego zmieniać. Jeśli musimy przekazać bufor do biblioteki, możemy przydzielić wektor i przekazaćv.data()
Nie możesz tego zrobić
std::vector
bez zrobienia kopii.std::vector
jest właścicielem wskaźnika, który ma pod maską i przydziela miejsce za pośrednictwem udostępnionego alokatora.Jeśli masz dostęp do kompilatora obsługującego C ++ 20, możesz użyć std :: span, który został zbudowany właśnie w tym celu. Zawija wskaźnik i rozmiar w „kontener”, który ma interfejs kontenera C ++.
Jeśli nie, możesz użyć gsl :: span, na której oparta była standardowa wersja.
Jeśli nie chcesz importować innej biblioteki, możesz to trywialnie zaimplementować samodzielnie, w zależności od tego, jaką funkcjonalność chcesz mieć.
źródło
Nie możesz. Nie po to
std::vector
jest.std::vector
zarządza własnym buforem, który jest zawsze pobierany od alokatora. Nigdy nie przejmuje własności innego bufora (z wyjątkiem innego wektora tego samego typu).Z drugiej strony nie musisz tego robić, ponieważ ...
Algorytmy te działają na iteratorach. Wskaźnik jest iteratorem tablicy. Nie potrzebujesz wektora:
W przeciwieństwie do szablonów funkcyjnych
<algorithm>
, niektóre narzędzia, takie jak zakres-zakres,std::begin
/std::end
i C ++ 20, nie działają jednak tylko z parą iteratorów, podczas gdy działają z kontenerami takimi jak wektory. Możliwe jest utworzenie klasy opakowania dla iteratora + rozmiaru, który zachowuje się jak zakres i działa z tymi narzędziami. C ++ 20 wprowadzi takie opakowanie do standardowej biblioteki:std::span
.źródło
Oprócz innych dobrych sugestii dotyczących
std::span
wejścia w c ++ 20, agsl:span
także włączenie własnej (lekkiej)span
klasy do tego czasu jest już dość łatwe (nie krępuj się skopiować):Na szczególną uwagę zasługuje także biblioteka boost-range, jeśli interesuje Cię bardziej ogólna koncepcja zakresu: https://www.boost.org/doc/libs/1_60_0/libs/range/doc/html/range/reference /utilities/iterator_range.html .
Koncepcje zasięgu pojawią się również w c ++ 20
źródło
using value_type = std::remove_cv_t<T>;
za?span(T* first_, size_t length) : first(first), length(length) {};
. Zredagowałem twoją odpowiedź.using value_type = std::remove_cv_t<T>;
to potrzebne przede wszystkim, jeśli jest używane z programowaniem szablonów (w celu uzyskania wartości_typu „zakresu”). Jeśli chcesz tylko użyć iteratorów, możesz to pominąć / usunąć.Rzeczywiście mógłby niemal używać
std::vector
do tego, nadużywając funkcji zwyczaj przydzielania powrócić wskaźnik do pamięci, którą chcesz wyświetlić. Nie byłoby to zagwarantowane przez normę do działania (wypełnienie, wyrównanie, inicjalizacja zwracanych wartości; trzeba będzie zadać sobie trud przy przypisywaniu początkowego rozmiaru, a dla osób niebędących prymitywami trzeba również zhakować konstruktorów ), ale w praktyce spodziewałbym się, że dostarczy wystarczająco dużo poprawek.Nigdy tego nie rób. Jest brzydki, zaskakujący, hacky i niepotrzebny. Algorytmy biblioteki standardowej są już zaprojektowane do pracy zarówno z surowymi tablicami, jak i wektorami. Szczegółowe informacje na ten temat można znaleźć w innych odpowiedziach.
źródło
vector
konstruktorami, które przyjmują niestandardowe odwołanie Allocator jako argument konstruktora (nie tylko parametr szablonu). Sądzę, że potrzebujesz obiektu alokującego, który zawiera wartość wskaźnika środowiska wykonawczego, a nie jako parametr szablonu, w przeciwnym razie mógłby działać tylko dla adresów constexpr. Trzeba będzie uważać, aby nie pozwolićvector
domyślnym konstruować obiektów.resize()
i zastępować istniejących danych; rozbieżność między właścicielem kontenera, takim jak wektor kontra zakres własności, jest ogromna, jeśli zaczniesz używać .push_back itp.construct
metodę, która byłaby wymagana ... Nie mogę myśleć, jakie niehackerskie przypadki użycia wymagałyby tego od umieszczenia nowego.resize()
Przed przekazaniem odwołania do czegoś, co chce użyć go jako czystego wyniku (np. Odczytu wywołania systemowego). W praktyce kompilatory często nie optymalizują tego zestawu ani nic takiego. Lub jeśli miałeś alokator, który używa calloc do uzyskania wstępnie wyzerowanej pamięci, możesz również uniknąć jej zabrudzenia, tak jak to głupiestd::vector<int>
domyślnie robi przy domyślnym konstruowaniu obiektów, które mają wzorzec zerowy. Patrz uwagi w en.cppreference.com/w/cpp/container/vector/vectorJak zauważyli inni,
std::vector
musi posiadać podstawową pamięć (brak bałaganu z niestandardowym alokatorem), więc nie można jej użyć.Inni zalecili także rozpiętość c ++ 20, ale oczywiście wymaga to c ++ 20.
Polecam Span-lite rozpiętości. Cytując jego podtytuł:
Zapewnia nieposiadający i modyfikowalny widok (jak można mutować elementy i ich kolejność, ale nie wstawiać ich), a jak mówi cytat, nie ma zależności i działa na większości kompilatorów.
Twój przykład:
Wydruki
To także ma dodany do góry nogami, gdy pewnego dnia zrobić przełącznik do C ++ 20, należy po prostu być w stanie zastąpić ten
nonstd::span
zstd::span
.źródło
Możesz użyć
std::reference_wrapper
dostępnego od C ++ 11:źródło
std::copy(std::begin(src_table), std::end(src_table), std::back_inserter(dest_vector));
zdecydowanie wypełniadest_vector
wartościami pobranymi zsrc_table
(IOW dane są kopiowanedest_vector
), więc nie dostałem twojego komentarza. Czy możesz wytłumaczyć?