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

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

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


person einpoklum    schedule 16.08.2017    source источник
comment
std::span был предложен в 2017 году. Он применяется к C ++ 17 или C ++ 20. Также см. P0122R5, span: bounds-safe views для последовательностей объектов. Вы действительно хотите настроить таргетинг на этот язык? Пройдут годы, прежде чем компиляторы наверстают упущенное.   -  person jww    schedule 24.01.2018
comment
@jww: span вполне можно использовать с C ++ 11 ... как gsl::span, а не std::span. См. Также мой ответ ниже.   -  person einpoklum    schedule 24.01.2018
comment
Также задокументировано на cppreference.com: en.cppreference.com/w/cpp/container/span   -  person Keith Thompson    schedule 24.03.2020
comment
@KeithThompson: Не в 2017 году ...   -  person einpoklum    schedule 25.03.2020
comment
@jww Все компиляторы теперь поддерживают std :: span ‹› в режиме C ++ 20. И span доступен во многих сторонних библиотеках. Вы были правы - это были годы: 2 года, если быть точным.   -  person Contango    schedule 20.06.2020


Ответы (2)


Что это?

A span<T> is:

  • Очень легкая абстракция непрерывной последовательности значений типа T где-то в памяти.
  • В основном это struct { T * ptr; std::size_t length; } с кучей удобных методов.
  • Тип, не являющийся владельцем (т.е. ссылочный тип, а не тип значения ): Он никогда ничего не выделяет и не освобождает и не поддерживает работу интеллектуальных указателей.

Ранее он назывался array_view, а еще раньше - _ 5_.

Когда мне его использовать?

Во-первых, когда нельзя его использовать:

  • Не используйте его в коде, который может принимать любую пару начальных и конечных итераторов, например 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);

Зачем мне его использовать? Почему это хорошо?

Ой, пролеты потрясающие! Использование _15 _...

  • означает, что вы можете работать с этой комбинацией указателя + длина / начало + конец указателя, как если бы вы работали с причудливым, расширенным контейнером стандартной библиотеки, например:

    • for (auto& x : my_span) { /* do stuff */ }
    • std::find_if(my_span.cbegin(), my_span.cend(), some_predicate);
    • std::ranges::find_if(my_span, some_predicate); (in C++20)

    ... но абсолютно без накладных расходов, связанных с большинством контейнерных классов.

  • позволяет компилятору иногда выполнять больше работы за вас. Например, это:

      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)

  • указывает, что ваш код (использующий диапазон) не владеет указанной памятью.

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

Но есть ли это в стандартной библиотеке?

edit: Да, std::span был добавлен в C ++ с версия языка C ++ 20!

Почему только в C ++ 20? Что ж, хотя идея не нова - ее нынешняя форма была задумана вместе с C ++ Основные принципы, который только начал формироваться в 2015 году. Так что на это потребовалось время.

Итак, как мне его использовать, если я пишу C ++ 17 или более раннюю версию?

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

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

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

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


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

person einpoklum    schedule 16.08.2017
comment
Разве read_into(int* from, int* to) не будет больше соответствовать обычному (основанному на итераторах) подходу стандартной библиотеки? - person Daniel Jour; 17.08.2017
comment
@DanielJour: Мне нужен минималистичный пример. И ваш, и мой варианты функций, вероятно, в любом случае не будут тем, что вы на самом деле поместили бы в библиотеку. Также обратите внимание, что в вашем варианте невозможно узнать, сколько вам нужно прочитать, так что, может быть, вы имеете в виду read_into(int* from, int* to, size_t n)? На самом деле это упоминается в документе Core Guidelines как плохой пример. - person einpoklum; 17.08.2017
comment
@einpoklum о, нет, я не это имел в виду. Другой вариант: read_into(int* begin, int* end) - person Daniel Jour; 17.08.2017
comment
@DanielJour: Ну что ж, дело в том, что с span<T> вы не знаете / не заботитесь, есть ли у него внутри int* begin; size_t length или int* begin; int* end;. Так что я не думаю, что это так уж важно. - person einpoklum; 17.08.2017
comment
Было бы разумнее стандартизировать общий диапазон (поддерживающий итератор + дозорный и итератор + длина, возможно, даже итератор + дозорный + длина) и сделать span простым typedef. Потому что, знаете, это более общий характер. - person Deduplicator; 17.08.2017
comment
Жалко, что они не стандартизировали представление, чтобы его можно было использовать для взаимодействия между разными языками. - person CodesInChaos; 17.08.2017
comment
@Deduplicator: Диапазоны прибывают в C ++, но текущее предложение (Эрика Ниблера) требует поддержки Concepts. Так что не раньше C ++ 20. - person einpoklum; 17.08.2017
comment
Можете ли вы объяснить, как span может узнать размер массива, когда он создается только указателем на массив? Это связано с тем, что span может читать байты рядом с указателем, чтобы получить информацию о размере (я предполагаю, что эти байты также выделяются new или malloc, поэтому они могут правильно delete это позже)? Я не могу найти ни одной статьи об этом, и подсказка приветствуется. Спасибо. - person Hải Phạm Lê; 22.08.2017
comment
@ HảiPhạmLê: Массивы не распадаются сразу на указатели. попробуйте сделать std::cout << sizeof(buffer) << '\n', и вы увидите, что получите 100 sizeof (int). - person einpoklum; 23.08.2017
comment
@einpoklum: Понятно, да, ты прав. Верно ли, что компилятор может определить размер буфера, поскольку он известен во время компиляции? sizeof также является функцией времени компиляции. Но как насчет памяти, динамически выделяемой new или malloc? Может span работать с ними? - person Hải Phạm Lê; 23.08.2017
comment
@ HảiPhạmLê: он может сказать размер буфера, потому что буфер определяется как массив размера. Это не имеет ничего общего с временем компиляции и временем выполнения. Прочтите ответы на этот вопрос здесь, в SO. - person einpoklum; 23.08.2017
comment
Самостоятельно отвечать на вопросы вполне приемлемо. - person ; 14.06.2018
comment
Каковы преимущества / недостатки по сравнению с std :: array? - person Jim; 07.11.2018
comment
@Jim std::array - это контейнер, он владеет ценностями. span не владеет - person Caleth; 04.12.2018
comment
@Jim: std::array - совсем другой зверь. Его длина фиксируется во время компиляции, и, как объяснил Калет, это тип значения, а не ссылочный тип. - person einpoklum; 04.12.2018
comment
Пример с read_into(buffer); - это ключевая проблема, с которой я здесь сталкиваюсь. Ссылка на собственный массив очень успешно убивает идею «диапазона» в этом сценарии. Это фундаментальный словарный тип, такой же, как и std :: span. Мне нужно, чтобы об этом было ясно сказано в ликовании «размаха». - person Chef Gladiator; 27.03.2019
comment
пример набора ards - person quantdev; 14.04.2019
comment
Преимущество реализации span Тристана Бриндла состоит в том, что он представляет собой только один заголовочный файл с несколькими строками кода, поддерживает C ++ 11 или новее и не зависит от других средств GSL. - person user5534993; 18.08.2019
comment
@ user5534993: Как этот комментарий улучшает то, что я уже написал? - person einpoklum; 18.08.2019
comment
Самым большим преимуществом по сравнению с контейнером STL, который может служить квази-span (например, array или vector), @Jim, вам не нужно неловко превращать его в ссылочный тип, сохраняя кучу указателей или reference_wrapper на данные, которые вы хотите просмотреть. - person Justin Time - Reinstate Monica; 18.08.2019
comment
В общем, span похож на string_view, но работает с любым типом, а не только с char, верно? - person Ruslan; 31.08.2019
comment
@Ruslan: Между ними есть сходство, но это не одно и то же. Есть существенные различия, по крайней мере, с точки зрения доступных методов. - person einpoklum; 31.08.2019
comment
набор удобных методов - довольно уничижительный термин для стандартного интерфейса коллекции, вам не кажется? - person Toby Speight; 03.03.2020
comment
@einpoklum, обновите ответ, чтобы подробно объяснить, чем span отличается от std::array, и какой из них лучше для чего и почему. - person Gabriel Staples; 14.04.2020
comment
@GabrielStaples: Я не буду. Здесь нет причин сосредотачиваться на std::array, в отличие от любого другого конкретного контейнера. Кроме того, если вы вообще знаете std::array, то ответ действительно объясняет, чем они отличаются. ИМХО. - person einpoklum; 14.04.2020
comment
Это не достаточно ясно в одном проходе для того, кто никогда раньше не слышал о диапазоне. Этого достаточно: замените второй пункт следующим: - Обычно single struct { T * ptr; std::size_t length; } с кучей удобных методов. (Обратите внимание, что это заметно отличается от std::array<>, потому что span включает удобные методы доступа, сопоставимые с std::array через указатель на тип T и длину типа T, тогда как std::array - это фактический контейнер, который содержит один или несколько значения типа T.). Выполнено. Это все, о чем я прошу. Это очень помогает. - person Gabriel Staples; 14.04.2020
comment
@GabrielStaples: нет причин сосредотачиваться на std :: array, а не, скажем, std::vector. - person einpoklum; 14.04.2020
comment
разумная альтернатива передаче константного вектора ‹T› & функциям, когда вы ожидаете, что ваши данные будут непрерывными в памяти. Может кто-нибудь расширить? Разве векторы не смежны в памяти? - person luizfls; 26.04.2020
comment
@luizfls: Есть. Но если это то, что нужно вашей функции - не настаивайте на том, чтобы вход был std::vector - подойдет любая непрерывная последовательность памяти, а диапазон - это просто контейнер для ее представления. - person einpoklum; 26.04.2020
comment
сначала вы говорите не использовать его с std::find_if, а затем используете его сами ... Я чувствую, что нужна другая формулировка - person Noone AtAll; 27.08.2020
comment
В частности, о cstring_span<>, этот конкретный диапазон не хранит никаких символьных данных, верно? Итак, гипотетически, если бы у меня был cstring_span<>, который, например, был выделен из std::string::c_str(), он также имел бы такое же время жизни, что и char* из c_str, верно? Какое значение добавляет cstring_span по сравнению с простой работой с char* или std::string? - person jrh; 21.10.2020
comment
@jrh: Спасибо, что заметили опечатку. В любом случае - я не знаю cstring_span; этого нет в стандартной библиотеке и нет в моем ответе. Но, как правило, промежутки не хранят никаких данных. И да, его время жизни такое же, как и у указателя. Какое значение он добавляет? - С одной стороны, он имеет длину, а с другой - это простая дешевая структура, не имеющая никакой памяти. - person einpoklum; 21.10.2020
comment
@einpoklum а, понятно, я не знал, что это нестандартно. Я заметил, что это было в старой версии GSL, я не был уверен, откуда это взялось. Спасибо! - person jrh; 21.10.2020
comment
«Не используйте его в коде, который может принимать любую пару начальных и конечных итераторов» - Почему? Разве std::span не избавляется от пар? - person Константин Ван; 01.01.2021
comment
@ КонстантинВан: Нет ... пара-итераторов - это гораздо более общий вопрос. Например: начало и конец std::list - это два итератора, но элементы не расположены в памяти подряд, поэтому они не подходят для диапазона. - person einpoklum; 01.01.2021

span<T> это:

template <typename T>
struct span
{
    T * ptr_to_array;   // pointer to a contiguous C-style array of data
                        // (which memory is NOT allocated or deallocated 
                        // by the span)
    std::size_t length; // number of elements in the array

    // Plus a bunch of constructors and convenience accessor methods here
}

Это легкая оболочка вокруг массива в стиле C, которую предпочитают разработчики C ++ всякий раз, когда они используют библиотеки C и хотят обернуть их контейнером данных в стиле C ++ для обеспечения безопасности типов и C ++ - ishness и feelgoodery. :)


Идти дальше:

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

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

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

ПРЯМАЯ ЦИТАТА ЕГО ОТВЕТА - С МОИ ДОБАВЛЕНИЯ и комментариями в скобках, выделенными жирным шрифтом и курсивом, выделенным мной:

Что это?

A span<T> is:

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

Ранее он назывался array_view < / a> и даже раньше как _21 _ .

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

Это НЕ КАК std::array<>, потому что std::array<> на самом деле выделяет память для всего непрерывного блока, и НЕ НРАВИТСЯ std::vector<>, потому что std::vector - это просто std::array, который также динамически растет (обычно удваивается в размере) каждый раз он заполняется, и вы пытаетесь добавить к нему что-то еще. std::array имеет фиксированный размер, а 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, если динамический. Диапазон просто указывает на память и упрощает доступ к ней, но НЕ управляет ею!):
  2. https://en.cppreference.com/w/cpp/container/span
  3. std::array<T, N> (обратите внимание, у него фиксированный размер N!):
  4. https://en.cppreference.com/w/cpp/container/array
  5. http://www.cplusplus.com/reference/array/array/
  6. std::vector<T> (автоматически динамически увеличивается в размере по мере необходимости):
  7. https://en.cppreference.com/w/cpp/container/vector
  8. 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#L153

Другие ссылки:

  1. Struct с шаблонными переменными в C ++
  2. Википедия: классы C ++
  3. видимость по умолчанию для членов класса / структуры C ++

Связанный:

  1. [еще один из моих ответов о шаблонах и диапазонах] Как сделать диапазон пролеты
person Gabriel Staples    schedule 14.04.2020
comment
Я бы действительно не рекомендовал использовать всю abseil для получения класса span. - person einpoklum; 28.08.2020
comment
понятно. Самое большое преимущество - легкий вес. - person yushang; 01.07.2021