Udostępnij za pośrednictwem


<ranges> klasy widoków

Widok to uproszczony zakres, który odwołuje się do elementów, których nie posiada (z wyjątkiem owning_view). Widok jest zwykle oparty na innym zakresie i zapewnia inny sposób przeglądania go, niezależnie od tego, czy jest on przekształcany, czy filtrowany. Na przykład std::views::filter jest to widok, który używa kryteriów, które określisz, aby wybrać elementy z innego zakresu.

Gdy uzyskujesz dostęp do elementów w widoku, odbywa się to "leniwie", dzięki czemu praca jest wykonywana tylko wtedy, gdy otrzymasz element. Dzięki temu można łączyć lub tworzyć widoki bez kary za wydajność.

Można na przykład utworzyć widok, który udostępnia tylko równe elementy z zakresu, a następnie przekształcić je przez ich kwadrat. Praca w celu przeprowadzenia filtrowania i przekształcania jest wykonywana tylko dla elementów, do których uzyskujesz dostęp, i tylko wtedy, gdy uzyskujesz do nich dostęp.

Widok można skopiować, przypisać i zniszczyć w stałym czasie bez względu na liczbę elementów, które zawiera. Dzieje się tak, ponieważ widok nie jest właścicielem elementów, do których się odwołuje, więc nie musi tworzyć kopii. Dlatego można tworzyć widoki bez kary za wydajność.

Zazwyczaj widok jest tworzony przy użyciu adaptera zakresu. Adaptery zakresów są zamierzonym sposobem tworzenia widoku, są łatwiejsze do użycia niż tworzenie wystąpień klas widoków bezpośrednio i czasami bardziej wydajne niż tworzenie wystąpień klas widoków bezpośrednio. Klasy widoków są widoczne bezpośrednio w przypadku konieczności utworzenia własnego niestandardowego typu widoku na podstawie istniejącego typu widoku.

Oto krótki przykład tworzenia widoku kwadratów elementów, które są podzielne przez trzy w wektora:

// requires /std:c++20 or later
#include <ranges>
#include <vector>
#include <iostream>

int main()
{
    std::vector<int> input =  {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    auto divisible_by_three = [](const int n) {return n % 3 == 0;};
    auto square = [](const int n) {return n * n;};

    auto x = input
             | std::views::filter(divisible_by_three)
             | std::views::transform(square);

    for (int i : x)
    {
        std::cout << i << ' '; // 0 9 36 81
    }
}
0 9 36 81

Użycie widoku po zakresie, na podstawie którego jest on oparty, może prowadzić do niezdefiniowanego zachowania. Na przykład reverse_view nie należy ponownie używać elementu opartego na wektorze, jeśli dodasz lub usuniesz elementy z wektora bazowego. Modyfikowanie wektora bazowego unieważnia iterator kontenera end — w tym kopię iteratora, którą mógł wykonać widok.

Ponieważ widoki są tanie do utworzenia, zazwyczaj należy ponownie utworzyć widok, jeśli zmodyfikujesz bazowy zakres. W poniższym przykładzie pokazano, jak przechowywać potok widoku w zmiennej, aby można było go użyć ponownie.

// requires /std:c++20, or later
#include <iostream>
#include <ranges>
#include <vector>
#include <list>
#include <string_view>
#include <algorithm>

template<typename rangeType>
void show(std::string_view msg, rangeType r)
{
    std::cout << msg;
    std::ranges::for_each(r,
        [](auto e)
        {
            std::cout << e << ' ';
        });
    std::cout << '\n';
}

int main()
{
    std::vector v{ 1, 2, 3, 4 };
    show("v: ", v);

    // You can save a view pipeline
    auto rev3 = std::views::take(3) | std::views::reverse;

    show("v | rev3: ", v | rev3); // 3 2 1

    v.insert(v.begin(), 0); // v = 0 1 2 3 4
    show("v: ", v);

    // Because modifying the vector invalidates its iterators, rebuild the view.
    // We are reusing the view pipeline we saved earlier
    show("v | rev3(v): ", rev3(v));
}
v: 1 2 3 4
v | rev3: 3 2 1
v: 0 1 2 3 4
v | rev3(v): 2 1 0

Następujące klasy widoków są zdefiniowane w std::ranges przestrzeni nazw.

Wyświetlanie opis
basic_istream_viewC++20 Widok kolejnych elementów ze strumienia wejściowego. Specjalizacje obejmują istream_view i wistream_view.
common_viewC++20 Dostosowuje widok z różnymi typami iteratora/sentinela do widoku z tymi samymi typami iteratora/sentinela.
drop_viewC++20 Utworzony z innego widoku, pomijając pierwsze count elementy.
drop_while_viewC++20 Utworzony z innego widoku pomijając elementy wiodące, o ile predykat jest przechowywany.
elements_viewC++20 Widok nad wybranym indeksem do każdej wartości podobnej do krotki w kolekcji. Na przykład, biorąc pod uwagę zakres std::tuple<string, int> wartości, utwórz widok składający się ze wszystkich string elementów z każdej krotki.
empty_viewC++20 Widok bez elementów.
filter_viewC++20 Filtruje elementy zakresu, które nie pasują do predykatu.
iota_viewC++20 Wygenerowany widok zawierający sekwencję wartości przyrostowych.
join_viewC++20 Łączy wszystkie elementy wielu zakresów w jeden widok.
keys_viewC++20 Widok pierwszego indeksu do każdej wartości podobnej do krotki w kolekcji. Na przykład, biorąc pod uwagę zakres std::tuple<string, int> wartości, utwórz widok składający się z string elementów z każdej krotki.
lazy_split_viewC++20 Dzieli widok na podgrupy na podstawie ogranicznika.
owning_viewC++20 Przejmuje własność elementów z innego zakresu.
ref_viewC++20 Widok odwołujący się do elementów należących do innego zakresu.
reverse_viewC++20 Przedstawia elementy zakresu w odwrotnej kolejności.
single_viewC++20 Widok zawierający tylko jeden element.
split_viewC++20 Dzieli widok na podgrupy na podstawie ogranicznika.
subrangeC++20 Widok części elementów zakresu, zgodnie z definicją iteratora rozpoczęcia i sentinel.
take_viewC++20 Zawiera określoną liczbę elementów pobranych z przodu zakresu.
take_while_viewC++20 Zawiera wiodące elementy zakresu, które pasują do danego predykatu.
transform_viewC++20 Widok podstawowej sekwencji po zastosowaniu funkcji przekształcania do każdego elementu.
values_viewC++20 Widok na drugi indeks do każdej wartości podobnej do krotki w kolekcji. Na przykład, biorąc pod uwagę zakres std::tuple<string, int> wartości, utwórz widok składający się z int elementów z każdej krotki.

Wiele z tych klas ma odpowiednie adaptery zakresów w std:views przestrzeni nazw, która tworzy ich wystąpienia. Preferuj używanie adaptera, aby utworzyć widok zamiast tworzyć klasy widoków bezpośrednio. Adaptery zakresowe są zamierzonym sposobem tworzenia widoków, łatwiejsze do użycia, a w niektórych przypadkach są bardziej wydajne.

Właściwości klas wyświetlania

Każdy temat klasy widoku ma sekcję Charakterystykę po sekcji składni. Sekcja Charakterystyka zawiera następujące wpisy:

  • Adapter zakresu: łącze do adaptera zakresu, który tworzy widok. Zazwyczaj używasz adaptera zakresu, aby utworzyć widok, a nie bezpośrednio utworzyć klasę widoku, więc jest ona wymieniona tutaj dla wygody.

  • Zakres bazowy: widoki mają różne wymagania iteracyjne dla rodzaju bazowego zakresu, którego mogą używać. Zobacz hierarchię iteratora zakresów, aby uzyskać więcej informacji na temat rodzajów iteratorów.

  • Kategoria iteratora widoku: kategoria iteratora widoku. Gdy widok dostosowuje zakres, typ iteratora widoku jest zwykle taki sam jak typ iteratora bazowego zakresu. Jednak może się to różnić w przypadku niektórych widoków. Na przykład reverse_view element ma wartość bidirectional_iterator, nawet jeśli zakres bazowy ma random_access_iteratorwartość .

  • Typ elementu: typ elementów zwracanych przez iterator widoku.

  • Rozmiar: czy widok może zwracać liczbę elementów, do których się odwołuje. Nie wszystkie widoki mogą.

  • Wspólny zakres: określa, czy widok jest typem common_range, co oznacza, że typy iteratora rozpoczęcia i sentinel są takie same. Typowe zakresy są przydatne w przypadku kodu wstępnego, który współpracuje z parami iteratora. Przykładem są konstruktory par iteratora dla kontenera sekwencji, na przykład vector(ranges::begin(x), ranges::end(x)).

  • Zakres pożyczony: określa, czy widok jest pożyczonym zakresem. borrowed_range<T> oznacza, że można używać iteratorów dla T po T zniszczeniu.

    Żaden standardowy kontener nie jest pożyczonym zakresem, ponieważ niszczenie kontenera zwalnia elementy i unieważnia wszystkie iteratory. W takim przypadku mówimy, że iteratory pozostają "zwisające" po zniszczeniu.

    Na przykład std::ranges::find() zwykle zwraca iterator do znalezionego elementu w argumencie zakresu. Jeśli argument zakresu jest kontenerem tymczasowym (rvalue), błędem jest przechowywanie zwróconego iteratora i użycie go później, ponieważ jest to "zwisające".

    Algorytmy zakresu zwracające iteratory (lub podranges) robią to tylko wtedy, gdy ich argumenty są lvalues (non-temporaries) lub pożyczone zakresy. W przeciwnym razie zwracają std::dangling obiekt, który zawiera wskazówkę w komunikatach o błędach dotyczących tego, co poszło nie tak, jeśli próbowano go użyć jak iterator.

  • Jest const iterowalne: wskazuje, czy można iterować const w wystąpieniu widoku. Nie wszystkie const widoki można iterować. Jeśli widok nie const jest iterowalny, nie można wykonać iteracji for (const auto& element : as_const(theView)) ani przekazać go do funkcji, która przyjmuje const odwołanie do widoku, a następnie próbuje wykonać iterację nad nim.

Hierarchia iteratora zakresów

W sekcji Właściwości każdego tematu klasy widoku kategoria iteratora w kategorii Zakres bazowy i Widok iteratora odnosi się do rodzaju iteratora obsługiwanego przez zakres/widok. Istnieją sześć kategorii iteratorów zakresów, które są identyfikowane przez koncepcje języka C++20. Hierarchia iteratorów zakresu, w kolejności rosnącej możliwości, jest:

Koncepcja iteratora zakresu opis
output_range Tylko zapis, tylko przechodzi do przodu; jednoprzepustowe.
input_range Tylko do odczytu, tylko przechodzi do przodu; jednoprzepustowe.
forward_range Porusza się tylko do przodu; multi-pass.
bidirectional_range Może iść do przodu i do tyłu; multi-pass.
random_access_range Może uzyskać dostęp do kolekcji za pomocą indeksu; multi-pass.
contiguous_range Może uzyskiwać dostęp do kolekcji za pomocą indeksu, a elementy są przechowywane stale w pamięci.

Ogólnie rzecz biorąc, iterator ma możliwość iteratorów poprzedzających ją w tabeli. Na przykład bidirectional_range ma możliwości forward_range, ale nie odwrotnie. Z wyjątkiem input_range, który nie ma możliwości, output_range ponieważ nie można zapisać w pliku input_range.

Instrukcja "wymaga input_range lub wyższa" oznacza, że widok może być używany z iteratorem input_range, forward_range, bidirectional_range, lub random_access_rangecontiguous_range , ponieważ są one tak zdolne, jak input_range.

Hierarchia iteratora zakresów jest bezpośrednio powiązana z hierarchią iteratora. Aby uzyskać więcej informacji, zobacz Pojęcia iteracyjne.

Zobacz też

<ranges>
Adaptery zakresowe