Что такое «промежуток» и когда я должен его использовать?

238

Недавно я получил предложения использовать span<T>их в своем коде или увидел здесь некоторые ответы на сайте, которые используют span- предположительно, какой-то контейнер. Но - я не могу найти ничего подобного в стандартной библиотеке C ++ 17.

Так что же это за таинственное span<T>и почему (или когда) стоит использовать его, если оно нестандартно?

einpoklum
источник
std::spanбыл предложен в 2017 году. Это относится к C ++ 17 или C ++ 20. Также см. P0122R5, span: безопасные для границ представления для последовательностей объектов . Вы действительно хотите нацелиться на этот язык? Пройдут годы, прежде чем компиляторы догонят.
jww
6
@jww: span вполне подходят для C ++ 11 ... gsl::spanа не для std::span. Смотрите также мой ответ ниже.
einpoklum
Также задокументировано на cppreference.com: en.cppreference.com/w/cpp/container/span
Кит Томпсон,
1
@KeithThompson: Не в 2017 году это не было ...
einpoklum
@jww Все компиляторы теперь поддерживают std :: span <> в режиме C ++ 20. И span доступен от многих сторонних библиотек. Вы были правы - это были годы: если быть точным, 2 года.
Контанго

Ответы:

273

Что это?

А span<T>это:

  • Очень легкая абстракция непрерывной последовательности значений типа Tгде-то в памяти.
  • В основном struct { T * ptr; std::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) не принадлежит указанная память.

Есть еще больше мотивов для использования spans, которые вы можете найти в основных рекомендациях C ++ - но вы поймете, что это дрейф.

Почему его нет в стандартной библиотеке (по состоянию на C ++ 17)?

Он есть в стандартной библиотеке - но только на C ++ 20. Причина в том, что он все еще довольно новый в своей нынешней форме, задуманный в связи с проектом основных рекомендаций C ++ , который формируется только с 2015 года. (Хотя, как отмечают комментаторы, он имеет более раннюю историю).

Так как мне его использовать, если его еще нет в стандартной библиотеке?

Это часть библиотеки поддержки основных руководящих принципов (GSL). Реализации:

  • GSL от Microsoft / Нила Макинтоша содержит отдельную реализацию:gsl/span
  • GSL-Lite - это реализация всего GSL с одним заголовком (он не такой большой, не волнуйтесь), в том числе span<T>.

Реализация GSL обычно предполагает платформу, которая реализует поддержку C ++ 14 [ 14 ]. Эти альтернативные реализации с одним заголовком не зависят от средств GSL:

  • martinmoene/span-lite требуется C ++ 98 или более поздняя версия
  • tcbrindle/span требуется C ++ 11 или более поздняя версия

Обратите внимание, что эти разные реализации span имеют некоторые различия в том, какие методы / вспомогательные функции они поставляют; и они также могут несколько отличаться от версии, входящей в стандартную библиотеку C ++ 20.


Дальнейшее чтение: Вы можете найти все детали и конструктивные соображения в окончательном официальном предложении до C ++ 17, P0122R7: span: безопасные для границ представления для последовательностей объектов Нила Макинтоша и Стефана Дж. Лававея. Это немного долго, хотя. Кроме того, в C ++ 20 изменилась семантика сравнения диапазонов (после этой короткой статьи Тони ван Эрда).

einpoklum
источник
2
Было бы разумнее стандартизировать общий диапазон (поддерживая итератор + сторож и итератор + длина, может быть, даже итератор + сторож + длина) и сделать span простым typedef. Потому что, вы знаете, это более общее.
Дедупликатор
3
@Deduplicator: диапазоны приходят в C ++, но текущее предложение (Эрик Ниблер) требует поддержки концепций. Так что не раньше C ++ 20.
einpoklum
8
@ HảiPhạmLê: Массивы не сразу распадаются на указатели. попробуйте сделать, std::cout << sizeof(buffer) << '\n'и вы увидите, что вы получите 100 sizeof (int).
einpoklum
4
@Jim std::array- это контейнер, он владеет значениями. spanне владеет
Caleth
3
@Jim: std::arrayэто совершенно другой зверь. Как пояснил Калет, его длина фиксируется во время компиляции, и это тип значения, а не ссылочный тип.
einpoklum
1

@einpoklum неплохо показывает, что spanесть в его ответе . Тем не менее, даже после прочтения его ответа, для новичков в спанах будет легко иметь последовательность вопросов для обсуждения, на которые нет полного ответа, таких как следующее:

  1. Чем spanотличается от массива C? Почему бы просто не использовать один из них? Похоже, что это только один из известных размеров ...
  2. Подождите, это звучит как std::array, как spanотличается от этого?
  3. О, это напоминает мне, не std::vectorкак у std::arrayтоже?
  4. Я весьма озадачен. Что это span?

Итак, вот дополнительная ясность по этому поводу:

ПРЯМАЯ ЦИТАТА ЕГО ОТВЕТА - С МОИМИ ДОПОЛНЕНИЯМИ, выделенными жирным шрифтом :

Что это?

А span<T>это:

  • Очень легкая абстракция непрерывной последовательности значений типа Tгде-то в памяти.
  • В основном единственная структура { T * ptr; std::size_t length; }с кучей удобных методов. (Обратите внимание, что это явно отличается от того, std::array<>что a spanвключает удобные методы доступа, сравнимые с std::arrayпомощью указателя на типT и длину (количество элементов) типа T, тогда как std::arrayпредставляет собой фактический контейнер, который содержит одно или несколько значений типа T.)
  • Несобственный тип (т. Е. «Ссылочный тип», а не «тип значения»): он никогда ничего не выделяет и не освобождает и не поддерживает работу умных указателей.

Ранее он был известен как array_viewи даже раньше array_ref.

Эти смелые части имеют решающее значение для понимания, так что не пропустите их или не прочитайте их! A spanНЕ является C-массивом структур и не является структурой C-массива типа Tплюс длина массива (по сути, это будет std::array контейнер ), NOR это C-массив структур указателей. к типу Tплюс длина, но скорее это единственная структура, содержащая один единственный указатель на типT , и длину , которая является количеством элементов (типа T) в непрерывном блоке памяти, на который Tуказывает указатель на тип ! Таким образом, единственные накладные расходы, которые вы добавили, используяspanпеременные для хранения указателя и длины, а также любые удобные функции доступа, которые вы используете span.

Это UNLIKE a, std::array<>потому что std::array<>фактически выделяет память для всего смежного блока, и это UNLIKE, std::vector<>потому что a std::vectorв основном просто a, std::arrayкоторый также выполняет динамический рост (обычно удваивается в размере) каждый раз, когда он заполняется, и вы пытаетесь добавить что-то еще к нему , A std::arrayимеет фиксированный размер, и a spanдаже не управляет памятью блока, на который он указывает, он просто указывает на блок памяти, знает, как долго находится блок памяти, знает, какой тип данных находится в C-массиве в памяти, и предоставляет удобные функции доступа для работы с элементами в этой смежной памяти .

Она является частью C ++ стандарт:

std::spanявляется частью стандарта C ++ с C ++ 20. Вы можете прочитать его документацию здесь: https://en.cppreference.com/w/cpp/container/span . Чтобы узнать , как использовать Google, absl::Span<T>(array, length)в C ++ 11 или более поздней версии сегодня , смотрите ниже.

Краткое описание и ключевые ссылки:

  1. std::span<T, Extent>( Extent= «количество элементов в последовательности, или std::dynamic_extentесли оно динамическое». Пролёт просто указывает на память и облегчает доступ, но НЕ управляет этим!):
    1. https://en.cppreference.com/w/cpp/container/span
  2. std::array<T, N>(обратите внимание, что он имеет фиксированный размер N!):
    1. https://en.cppreference.com/w/cpp/container/array
    2. http://www.cplusplus.com/reference/array/array/
  3. std::vector<T> (автоматически увеличивается в размере по мере необходимости):
    1. https://en.cppreference.com/w/cpp/container/vector
    2. http://www.cplusplus.com/reference/vector/vector/

Как я могу использовать spanв C ++ 11 или более поздней версии сегодня ?

Google открыл свои внутренние библиотеки C ++ 11 в форме своей библиотеки «Abseil». Эта библиотека предназначена для обеспечения функций C ++ 14 - C ++ 20 и более, которые работают в C ++ 11 и более поздних версиях, так что вы можете использовать функции завтрашнего дня уже сегодня. Они говорят:

Совместимость со стандартом C ++

Google разработал много абстракций, которые либо соответствуют, либо близко соответствуют функциям, включенным в C ++ 14, C ++ 17 и выше. Использование версий Abseil этих абстракций позволяет вам получить доступ к этим функциям сейчас, даже если ваш код еще не готов к жизни в мире, выпущенном после C ++ 11.

Вот некоторые ключевые ресурсы и ссылки:

  1. Основной сайт: https://abseil.io/
  2. https://abseil.io/docs/cpp/
  3. Репозиторий GitHub: https://github.com/abseil/abseil-cpp
  4. span.hзаголовок и absl::Span<T>(array, length)класс шаблона: https://github.com/abseil/abseil-cpp/blob/master/absl/types/span.h#L189
Габриэль Стейплс
источник
1
Я думаю, что вы приносите важную и полезную информацию, спасибо!
Гуй Лима