Co to jest „zakres” i kiedy powinienem go użyć?

237

Ostatnio dostałem sugestie dotyczące użycia span<T>w moim kodzie lub widziałem tutaj odpowiedzi na stronie, które używają span- podobno jakiegoś kontenera. Ale - nie mogę znaleźć czegoś takiego w standardowej bibliotece C ++ 17.

Czym jest to tajemnicze span<T>i dlaczego (lub kiedy) warto go używać, jeśli jest niestandardowy?

einpoklum
źródło
std::spanzostał zaproponowany w 2017 r. Dotyczy C ++ 17 lub C ++ 20. Zobacz także P0122R5, span: bezpieczne widoki dla sekwencji obiektów . Czy naprawdę chcesz kierować na ten język? Miną lata, zanim kompilatory nadrobią zaległości.
jww
6
@jww: span są całkiem użyteczne w C ++ 11 ... gsl::spanzamiast std::span. Zobacz także moją odpowiedź poniżej.
einpoklum
Udokumentowane również na cppreference.com: en.cppreference.com/w/cpp/container/span
Keith Thompson
1
@KeithThompson: Nie w 2017 roku nie było ...
einpoklum
@jww Wszystkie kompilatory obsługują teraz std :: span <> w trybie C ++ 20. Zakres jest dostępny w wielu bibliotekach stron trzecich. Miałeś rację - to były lata: dokładnie 2 lata.
Contango,

Odpowiedzi:

272

Co to jest?

A span<T>to:

  • Bardzo lekka abstrakcja ciągłej sekwencji wartości typu Tgdzieś w pamięci.
  • Zasadniczo struct { T * ptr; std::size_t length; }z wieloma wygodnymi metodami.
  • Typ niebędący właścicielem (tj. „Typ odniesienia” zamiast „typ wartości”): nigdy nie przydziela ani nie zwalnia niczego i nie utrzymuje inteligentnych wskaźników.

Wcześniej był znany jako, array_viewa jeszcze wcześniej jako array_ref.

Kiedy powinienem go użyć?

Po pierwsze, kiedy go nie używać:

  • Nie należy go używać w kodzie, który może po prostu podjąć każdą parę początkowy i końcowy iteratorów, jak std::sort, std::find_if, std::copyi wszystkich tych funkcji na matrycy Super-generic.
  • Nie używaj go, jeśli masz standardowy kontener biblioteki (lub kontener Boost itp.), O którym wiesz, że jest odpowiedni dla twojego kodu. Nie ma na celu zastąpienia żadnego z nich.

Teraz, kiedy go używać:

Użyj span<T>(odpowiednio span<const T>) zamiast wolnostojącego T*(odpowiednio const T*), dla którego masz wartość długości. Więc zamień funkcje takie jak:

  void read_into(int* buffer, size_t buffer_size);

z:

  void read_into(span<int> buffer);

Dlaczego powinienem tego używać? Dlaczego to dobrze?

Och, rozpiętości są niesamowite! Korzystanie z span...

  • oznacza, że ​​możesz pracować z tą kombinacją wskaźnika + długości / początku + końca, tak jak w przypadku fantazyjnego, wypchanego standardowego kontenera biblioteki, np .:

    • for (auto& x : my_span) { /* do stuff */ }
    • std::find_if(my_span.begin(), my_span.end(), some_predicate);

    ... ale absolutnie żadna z ogólnych kosztów, jakie ponosi większość klas kontenerów.

  • pozwala czasami kompilatorowi wykonać więcej pracy. Na przykład:

    int buffer[BUFFER_SIZE];
    read_into(buffer, BUFFER_SIZE);

    staje się to:

    int buffer[BUFFER_SIZE];
    read_into(buffer);

    ... który zrobi to, co chcesz. Zobacz także wytyczną P.5 .

  • jest rozsądną alternatywą dla przekazywania const vector<T>&do funkcji, gdy oczekuje się, że dane będą ciągłe w pamięci. Nigdy więcej besztany przez potężnych guru C ++!

  • ułatwia analizę statyczną, więc kompilator może pomóc w wykryciu głupich błędów.
  • pozwala na oprzyrządowanie kompilacji debugowania do sprawdzania granic środowiska wykonawczego (tj. spanmetody będą miały kod sprawdzający ograniczenia w #ifndef NDEBUG... #endif)
  • wskazuje, że twój kod (który korzysta z zakresu) nie posiada wskazanej pamięci.

Jest jeszcze więcej motywacji do używania spans, co można znaleźć w podstawowych wytycznych C ++ - ale łapiesz dryf.

Dlaczego nie ma go w standardowej bibliotece (od C ++ 17)?

Jest w standardowej bibliotece - ale tylko od C ++ 20. Powodem jest to, że wciąż jest całkiem nowy w swojej obecnej formie, opracowanej w połączeniu z projektem głównych wytycznych C ++ , który kształtuje się dopiero od 2015 r. (Chociaż, jak podkreślają komentatorzy, ma wcześniejszą historię).

Jak więc go użyć, jeśli nie ma go jeszcze w standardowej bibliotece?

Jest częścią Biblioteki wsparcia podstawowych wytycznych (GSL). Realizacje:

  • Pakiet GSL Microsoft / Neil Macintosh zawiera autonomiczną implementację:gsl/span
  • GSL-Lite to implementacja całego GSL z jednym nagłówkiem (nie jest tak duża, nie martw się), w tym span<T>.

Implementacja GSL zasadniczo zakłada platformę, która implementuje obsługę C ++ 14 [ 14 ]. Te alternatywne implementacje z jednym nagłówkiem nie zależą od funkcji GSL:

Zauważ, że te różne implementacje zakresu mają pewne różnice w tym, z jakimi metodami / funkcjami wsparcia pochodzą. i mogą się nieco różnić od wersji wchodzącej do standardowej biblioteki w C ++ 20.


Dalsza lektura: Wszystkie szczegóły i rozważania projektowe można znaleźć w ostatecznej oficjalnej propozycji przed C ++ 17, P0122R7: span: bezpieczne dla sekwencji widoki obiektów Neala Macintosha i Stephana J. Lavaveja. Trochę to jednak trwa. Również w C ++ 20 zmieniła się semantyka porównania rozpiętości (po tym krótkim artykule Tony van Eerda).

einpoklum
źródło
2
Bardziej sensowne byłoby ujednolicenie ogólnego zakresu (obsługa iteratora + wartownika i iteratora + długości, może nawet iteratora + wartownika + długości) i uczynienie span prostą czcionką. Ponieważ wiesz, to jest bardziej ogólne.
Deduplicator
3
@Deduplicator: Do C ++ nadchodzą zakresy, ale obecna propozycja (autorstwa Erica Nieblera) wymaga obsługi Koncepcji. Więc nie wcześniej niż C ++ 20.
einpoklum
8
@ HảiPhạmLê: Tablice nie rozpadają się od razu na wskaźniki. spróbuj zrobić, std::cout << sizeof(buffer) << '\n'a zobaczysz, że masz 100 sizeof (int).
einpoklum
4
@Jim std::arrayjest kontenerem, posiada wartości. spannie jest właścicielem
Caleth,
3
@Jim: std::arrayjest zupełnie inną bestią. Jego długość jest ustalona w czasie kompilacji i jest to typ wartości, a nie typ odniesienia, jak wyjaśnił Caleth.
einpoklum
1

@einpoklum ma całkiem niezłą robotę, przedstawiając to, co spanjest w swojej odpowiedzi tutaj . Jednak nawet po przeczytaniu jego odpowiedzi łatwo jest komuś nowemu, aby nadal miał sekwencję pytań, na które nie ma pełnej odpowiedzi, na przykład:

  1. Czym spanróżni się od tablicy C? Dlaczego nie skorzystać z jednego z nich? Wygląda na to, że to tylko jeden ze znanych rozmiarów ...
  2. Czekaj, to brzmi jak std::array, czym się spanróżni od tego?
  3. Och, to przypomina mi, nie jest std::vectorniczym std::arrayzbyt?
  4. Jestem zmieszany. :( Co to jest span?

Oto kilka dodatkowych wyjaśnień na ten temat:

BEZPOŚREDNI WYCENA JEGO ODPOWIEDZI - Z MOIMMI DODATKAMI :

Co to jest?

A span<T>to:

  • Bardzo lekka abstrakcja ciągłej sekwencji wartości typu Tgdzieś w pamięci.
  • Zasadniczo pojedyncza struktura { T * ptr; std::size_t length; }z wieloma wygodnymi metodami. (Zauważ, że różni się to wyraźnie od tego, std::array<>że spanumożliwia metody akcesorium wygodnego, porównywalne do std::array, za pomocą wskaźnika do typuT i długości (liczby elementów) typu T, podczas gdy std::arrayjest faktycznym pojemnikiem, który zawiera jedną lub więcej wartości typu T).
  • Typ niebędący właścicielem (tj. „Typ odniesienia” zamiast „typ wartości”): nigdy nie przydziela ani nie zwalnia niczego i nie utrzymuje inteligentnych wskaźników.

Wcześniej był znany jako, array_viewa jeszcze wcześniej jako array_ref.

Te odważne części są kluczowe dla zrozumienia, więc nie przegap ich ani nie odczytaj ich źle! A spanNIE jest tablicą C struktur, ani nie jest strukturą typu C Tpowiększoną o długość tablicy (byłby to w zasadzie std::array kontener ), NOR jest to tablica C struktur wskaźników pisać Tplus długość, ale raczej jest to pojedyncza struktura zawierająca pojedynczy wskaźnik do pisaniaT oraz długość , która jest liczbą elementów (typu T) w ciągłym bloku pamięci, na który Twskazuje wskaźnik do pisania ! W ten sposób jedyny narzut dodany przy użyciuspanto zmienne do przechowywania wskaźnika i długości oraz wszelkie używane funkcje akcesoriów, które spanudostępnia.

Jest to w przeciwieństwie do std::array<>, ponieważ std::array<>rzeczywiście alokuje pamięć dla całego ciągły blok, a to jest podobna std::vector<>, ponieważ std::vectorjest w zasadzie tylko std::array, że również ma dynamiczny rośnie (zazwyczaj podwojenie wielkości) za każdym razem zapełnia się i spróbować dodać coś jeszcze do niego . A std::arrayma stały rozmiar, a a spannawet nie zarządza pamięcią bloku, na który wskazuje, po prostu wskazuje blok pamięci, wie, jak długi jest blok pamięci, wie, jaki typ danych znajduje się w macierzy C. w pamięci i zapewnia dodatkowe funkcje akcesorium do pracy z elementami w tej ciągłej pamięci .

To jest część C ++ standard:

std::spanjest częścią standardu C ++ od C ++ 20. Możesz przeczytać jego dokumentację tutaj: https://en.cppreference.com/w/cpp/container/span . Aby zobaczyć, jak korzystać z Google absl::Span<T>(array, length)w C ++ 11 lub później dzisiaj , patrz poniżej.

Opisy podsumowań i najważniejsze odniesienia:

  1. std::span<T, Extent>( Extent= "liczba elementów w sekwencji lub std::dynamic_extentdynamiczna". Rozpiętość wskazuje tylko na pamięć i ułatwia dostęp, ale NIE zarządza nią!):
    1. https://en.cppreference.com/w/cpp/container/span
  2. std::array<T, N>(zauważ, że ma ustalony rozmiar N!):
    1. https://en.cppreference.com/w/cpp/container/array
    2. http://www.cplusplus.com/reference/array/array/
  3. std::vector<T> (automatycznie dynamicznie rośnie w miarę potrzeb):
    1. https://en.cppreference.com/w/cpp/container/vector
    2. http://www.cplusplus.com/reference/vector/vector/

Jak mogę użyć spanw C ++ 11 lub później dzisiaj ?

Google ma otwarte źródła wewnętrznych bibliotek C ++ 11 w formie biblioteki „Abseil”. Ta biblioteka ma na celu dostarczanie funkcji od C ++ 14 do C ++ 20 i innych, które działają w C ++ 11 i późniejszych, abyś mógł dziś korzystać z funkcji jutra. Mówią:

Zgodność ze standardem C ++

Google opracował wiele abstrakcji, które pasują lub ściśle pasują do funkcji zawartych w C ++ 14, C ++ 17 i innych. Korzystanie z wersji Abseil tych abstrakcji umożliwia teraz dostęp do tych funkcji, nawet jeśli kod nie jest jeszcze gotowy do życia w świecie post C ++ 11.

Oto niektóre kluczowe zasoby i linki:

  1. Strona główna: https://abseil.io/
  2. https://abseil.io/docs/cpp/
  3. Repozytorium GitHub: https://github.com/abseil/abseil-cpp
  4. span.hnagłówek i absl::Span<T>(array, length)klasa szablonów: https://github.com/abseil/abseil-cpp/blob/master/absl/types/span.h#L189
Gabriel Staples
źródło
1
Myślę, że przywołujesz ważne i przydatne informacje, dzięki!
Gui Lima