Что такое "промежуток" и когда я должен его использовать?
Недавно я получил предложения по использованию span<T>
в моем коде, или видел некоторые ответы здесь на сайте, которые используют span
х - якобы какой-то контейнер. Но - я не могу найти ничего подобного в стандартной библиотеке C++.
Так что же это за таинственное span<T>
и почему (или когда) лучше использовать его, если он нестандартный?
4 ответа
Что это?
span<T>
является:
- Очень легкая абстракция непрерывной последовательности значений типа T где-то в памяти.
- В основном
struct { T * const ptr; size_t length; }
с кучей удобных методов. - Несобственный тип (т. Е. "Ссылочный тип", а не "тип значения"): он никогда ничего не выделяет и не освобождает и не поддерживает работу умных указателей.
Ранее он был известен как array_view
а еще раньше как array_ref
,
Когда я должен использовать это?
Во-первых, когда его не использовать:
- Не используйте его в коде, который может просто использовать любую пару начальных и конечных итераторов, например
std::sort
,std::find_if
,std::copy
и все эти супер-общие шаблонные функции. - Не используйте его, если у вас есть стандартный библиотечный контейнер (или контейнер Boost и т. Д.), Который, как вы знаете, подходит для вашего кода. Это не предназначено, чтобы вытеснить любого из них.
Теперь для того, когда фактически использовать это:
использование
span<T>
(соответственно,span<const T>
) вместо отдельно стоящегоT*
(соответственноconst T*
) для которого у вас есть значение длины. Итак, замените функции, такие как:void read_into(int* buffer, size_t buffer_size);
с:
void read_into(span<int> buffer);
Почему я должен использовать это? Почему это хорошо?
Ох, пролеты потрясающие! Используя span
...
означает, что вы можете работать с этой комбинацией указатель + длина / начало + конец указателя, как если бы вы работали со сложным контейнером стандартной библиотеки, например:
for (auto& x : my_span) { /* do stuff */ }
std::find_if(my_span.begin(), my_span.end(), some_predicate);
... но абсолютно без каких-либо накладных расходов, которые несет большинство контейнерных классов.
позволяет компилятору иногда выполнять за вас больше работы. Например, это:
int buffer[BUFFER_SIZE]; read_into(buffer, BUFFER_SIZE);
становится так:
int buffer[BUFFER_SIZE]; read_into(buffer);
... который будет делать то, что вы хотели бы это сделать. См. Также Рекомендацию P.5.
является разумной альтернативой для прохождения
const vector<T>&
к функциям, когда вы ожидаете, что ваши данные будут непрерывными в памяти. Больше не надо ругать могучих гуру C++.облегчает статический анализ, поэтому компилятор может помочь вам обнаружить глупые ошибки.
- позволяет использовать инструменты отладки и компиляции для проверки границ во время выполнения (т.е.
span
методы будут иметь некоторый код проверки границ внутри#ifndef NDEBUG
...#endif
) - указывает, что ваш код (который использует span) не владеет указателем.
Там еще больше мотивации для использования span
s, которые вы могли бы найти в основных рекомендациях C++ - но вы поймете суть.
Почему его нет в стандартной библиотеке (по состоянию на C++17)?
Он есть в стандартной библиотеке - но только на C++20. Причина в том, что он все еще довольно новый в своей нынешней форме, задуманный в связи с проектом основных рекомендаций C++, который формируется только с 2015 года. (Хотя, как отмечают комментаторы, он имеет более раннюю историю).
Так как мне его использовать, если его еще нет в стандартной библиотеке?
Это часть библиотеки поддержки основных руководящих принципов (GSL). Реализации:
- GSL от Microsoft / Нила Макинтоша содержит отдельную реализацию:
gsl/span
- GSL-Lite - это реализация всего GSL в одном файле (не так уж и много, не волнуйтесь), включая
span<T>
,
Обратите внимание, что вы можете использовать его с более ранними версиями стандарта языка - C++11 и C++14, а не только с C++17.
Дальнейшее чтение: все подробности и конструктивные соображения можно найти в окончательном (?) Официальном предложении, P0122R7: span: безопасные для границ представления для последовательностей объектов Нила Макинтоша и Стефана Дж. Лававея. Это немного долго, хотя.
@einpoklum does a pretty good job of introducing what a span
is in his answer here. However, even after reading his answer, it is easy for someone new to spans to still have a sequence of stream-of-thought questions which aren't fully answered, such as the following:
- How is a
span
different from a C array? Why not just use one of those? It seems like it's just one of those with the size known as well... - Wait, that sounds like a
std::array
, how is aspan
different from that? - Oh, that reminds me, isn't a
std::vector
like astd::array
too? - I'm so confused.:(What's a
span
?
So, here's some additional clarity on that:
DIRECT QUOTE OF HIS ANSWER--WITH MY ADDITIONS and parenthetical comments IN BOLD and my emphasis in italics:
What is it?
A
span<T>
is:
- A very lightweight abstraction of a contiguous sequence of values of type
T
somewhere in memory.- Basically a single struct
{ T * ptr; std::size_t length; }
with a bunch of convenience methods. (Notice this is distinctly different fromstd::array<>
because aspan
enables convenience accessor methods, comparable tostd::array
, via a pointer to typeT
and length (number of elements) of typeT
, whereasstd::array
is an actual container which holds one or more values of typeT
.)- A non-owning type (i.e. a "reference-type" rather than a "value type"): It never allocates nor deallocates anything and does not keep smart pointers alive.
It was formerly known as an
array_view
and even earlier asarray_ref
.
Those bold parts are critical to one's understanding, so don't miss them or misread them! A span
is NOT a C-array of structs, nor is it a struct of a C-array of type T
plus the length of the array (this would be essentially what the std::array
container is), NOR is it a C-array of structs of pointers to type T
plus the length, but rather it is a single struct containing one single pointer to type T
, and the length, which is the number of elements (of type T
) in the contiguous memory block that the pointer to type T
points to! In this way, the only overhead you've added by using a span
are the variables to store the pointer and length, and any convenience accessor functions you use which the span
provides.
This is UNLIKE a std::array<>
because the std::array<>
actually allocates memory for the entire contiguous block, and it is UNLIKE std::vector<>
because a std::vector
is basically just a std::array
that also does dynamic growing (usually doubling in size) each time it fills up and you try to add something else to it. A std::array
is fixed in size, and a span
doesn't even manage the memory of the block it points to, it just points to the block of memory, knows how long the block of memory is, knows what data type is in a C-array in the memory, and provides convenience accessor functions to work with the elements in that contiguous memory.
It is part of the C++ standard:
std::span
is part of the C++ standard as of C++20. You can read its documentation here: https://en.cppreference.com/w/cpp/container/span. To see how to use Google's absl::Span<T>(array, length)
in C++11 or later today, see below.
Summary Descriptions, and Key References:
std::span<T, Extent>
(Extent
= "the number of elements in the sequence, orstd::dynamic_extent
if dynamic". A span just points to memory and makes it easy to access, but does NOT manage it!):- https://en.cppreference.com/w/cpp/container/span
std::array<T, N>
(notice it has a fixed sizeN
!):- https://en.cppreference.com/w/cpp/container/array
- http://www.cplusplus.com/reference/array/array/
std::vector<T>
(automatically dynamically grows in size as necessary):- https://en.cppreference.com/w/cpp/container/vector
- http://www.cplusplus.com/reference/vector/vector/
How Can I Use span
in C++11 or later today?
Google has open-sourced their internal C++11 libraries in the form of their "Abseil" library. This library is intended to provide C++14 to C++20 and beyond features which work in C++11 and later, so that you can use tomorrow's features, today. They say:
Compatibility with the C++ Standard
Google has developed many abstractions that either match or closely match features incorporated into C++14, C++17, and beyond. Using the Abseil versions of these abstractions allows you to access these features now, even if your code is not yet ready for life in a post C++11 world.
Here are some key resources and links:
- Main site: https://abseil.io/
- https://abseil.io/docs/cpp/
- GitHub repository: https://github.com/abseil/abseil-cpp
span.h
header, andabsl::Span<T>(array, length)
template class: https://github.com/abseil/abseil-cpp/blob/master/absl/types/span.h
Ответ, предоставленный Einpoklum, великолепен, но мне пришлось углубиться в раздел комментариев, чтобы понять одну конкретную деталь, поэтому это предназначено как расширение для уточнения этой детали.
Во-первых, когда его не использовать:
Не используйте его в коде, который может принимать любую пару начальных и конечных итераторов, таких как std::sort, std::find_if, std::copy и все эти сверхуниверсальные шаблонные функции. Не используйте его, если у вас есть стандартный контейнер библиотеки (или контейнер Boost и т. д.), который, как вы знаете, подходит для вашего кода. Он не предназначен для замены любого из них.
Любая пара начальных и конечных итераторов, в отличие от начальных и конечных указателей непрерывного хранения.
Как человек, который редко соприкасается с внутренней частью итераторов, я ускользнул от меня во время чтения ответа о том, что итераторы могут выполнять итерацию по связанному списку, чего не могут простые указатели (и диапазон).
Промежуток является (или, скорее, имеет) указатель на данные, не принадлежащие этому промежутку. По сути, вы можете выполнять диапазонные операции над диапазоном, которые влияют на чужие данные.
Это означает, что вы можете писать такие вещи, как быстрая сортировка на месте:
void qs( span<T> data ) {
split(data);
qs( span{ data+0,sizeoffirst } );
qs( span{ data+sizeoffirst,sizeofsecond } );
или любое количество функций линейной алгебры в стиле Лапака с помощью рекурсии или параллелизма.