Що таке "проміжок" і коли я повинен його використовувати?


237

Нещодавно я отримував пропозиції використовувати span<T>"у своєму коді" або бачив відповіді на сайті, які використовують span"нібито" якийсь контейнер. Але я нічого подібного не можу знайти у стандартній бібліотеці C ++ 17.

Отже, що це за таємниче span<T>, і чому (або коли) це гарна ідея використовувати його, якщо це нестандартно?


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. А проміжок доступний у багатьох сторонніх губ. Ви мали рацію - точні були роки: 2 роки.
Контанго

Відповіді:


272

Що це?

A 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);
    

    ... що зробить те, що ви хотіли б це зробити. Див. Також Керівництво С.5 .

  • є розумною альтернативою переходу const vector<T>&до функцій, коли ви очікуєте, що ваші дані будуть суміжними в пам'яті. Більше не лаяться високомогутні гуру C ++!

  • полегшує статичний аналіз, тому компілятор може допомогти вам знайти нерозумні помилки.
  • дозволяє проводити інструментацію компіляції налагодження для перевірки меж виконання (тобто spanу методів буде якийсь код перевірки меж протягом #ifndef NDEBUG... #endif)
  • вказує на те, що ваш код (для якого використовується "span") не має пам'ять, що вказує.

Є ще більша мотивація використання spans, яку ви можете знайти в основних інструкціях C ++ - але ви відчуваєте дрейф.

Чому його немає в стандартній бібліотеці (станом на C ++ 17)?

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

Тож як я можу його використовувати, якщо його ще немає у стандартній бібліотеці?

Це частина бібліотеки підтримки основних керівних принципів (GSL). Виконання:

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

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

Зауважте, що ці різні інтервальні реалізації мають деякі відмінності в методах / функціях підтримки, з якими вони входять; і вони також можуть дещо відрізнятися від версії, що надходить у стандартну бібліотеку в C ++ 20.


Подальше читання: Ви можете знайти всі деталі та міркування щодо дизайну в остаточній офіційній пропозиції до C ++ 17, P0122R7: span: вигляд для меж послідовностей об'єктів Ніла Макінтоша та Стефана Дж. Лававей. Хоча це трохи довго. Крім того, у C ++ 20 змінилася семантика порівняльного діапазону (після цієї короткої статті Тоні ван Еерда).


2
Було б більше сенсу стандартизувати загальний діапазон (підтримуючи ітератор + дозорний і ітератор + довжина, можливо навіть ітератор + дозорний + довжина) і зробити проліт простим typedef. Бо, знаєте, це більш загальне.
Дедуплікатор

3
@Deduplicator: Діапазони підходять до C ++, але поточна пропозиція (Ерік Ніблер) вимагає підтримки Concepts. Тож не раніше C ++ 20.
einpoklum

8
@ HảiPhạmLê: Масиви не відразу розпадаються на покажчики. спробуйте зробити, std::cout << sizeof(buffer) << '\n'і ви побачите, що ви отримаєте 100 sizeof (int) s.
einpoklum

4
@Jim std::array- контейнер, йому належать значення. spanне є власником
Калет

3
@Jim: std::arrayзовсім інший звір. Її довжина фіксується під час компіляції, і це тип типу, а не тип опорного типу, як пояснив Кейлет.
einpoklum

1

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

  1. Чим spanвідрізняється масив С? Чому б просто не скористатись одним із таких? Здається, що це просто одна з тих, хто також знає розмір ...
  2. Зачекайте, це звучить як std::array, чим spanвідрізняється від цього?
  3. О, це мені нагадує, це теж не std::vectorсхоже std::array?
  4. Я так розгубився. :( Що таке span?

Отже, ось дещо додаткової ясності щодо цього:

Пряма цитата його відповіді - з моїми доповненнями жирним шрифтом :

Що це?

A span<T>це:

  • Дуже легка абстракція суміжної послідовності значень типу Tдесь у пам'яті.
  • В основному це одна структура { T * ptr; std::size_t length; }з купою зручних методів. (Зауважте, це суттєво відрізняється від того, std::array<>що 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<>тому що в std::vectorосновному це просто std::arrayте, що також робить динамічне зростання (як правило, подвоєння в розмірах) щоразу, коли воно заповнюється, і ви намагаєтесь додати щось інше до нього . std::arrayФіксується в розмірі, і навіть не управління пам'яттю блоку він вказує, що тільки вказує на блок пам'яті, знає , як довго блок пам'яті, знає , який тип даних в C-масиву в пам’яті та забезпечує зручні функції аксесуара для роботи з елементами цієї суміжної пам’яті .span

Він є частиною стандарту 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
Думаю, ви наводите важливу та корисну інформацію, дякую!
Gui Lima
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.