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

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

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

Ответ 1

Что это?

A span<T> это:

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

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

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

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

  • Не используйте его в коде, который может занять любую пару start & конечные итераторы, такие как std::sort, std::find_if, std::copy и все эти супер-общие шаблонные функции.
  • Не используйте его, если у вас есть стандартный библиотечный контейнер (или контейнер Boost и т.д.), Который, как вы знаете, подходит для вашего кода. Он не предназначен для вытеснения любого из них.

Теперь о том, когда на самом деле использовать его:

Use span<T> (respectively, span<const T>) instead of a free-standing T* (respectively const T*) for which you have the length value. So, replace functions like:

  void read_into(int* buffer, size_t buffer_size);

with:

  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, которые вы можете найти в C++ основных принципах - но вы поймете, что это дрейф.

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

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

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

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

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

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

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

Обратите внимание, что вы можете использовать его с более ранними версиями языкового стандарта - C++ 11 и C++ 14, а не только с C++ 17.


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