std::span в C++20: безопасная передача массивов и векторов в функции
Поисковый запрос, под который написана статья: std::span C++20 примеры. Если коротко, std::span — это «окно» на непрерывный участок памяти: массив, std::array, std::vector или часть контейнера. Он не владеет данными, не выделяет память и почти ничего не стоит по производительности, но делает интерфейсы функций заметно чище.
Проблема: указатель и размер легко перепутать
До C++20 часто писали так:
#include <iostream>
int sum(const int* data, std::size_t size) {
int result = 0;
for (std::size_t i = 0; i < size; ++i) {
result += data[i];
}
return result;
}
int main() {
int numbers[] = {1, 2, 3, 4};
std::cout << sum(numbers, 4) << std::endl;
}
Работает, но есть минусы: можно передать неправильный размер, можно передать nullptr, а сама сигнатура функции не говорит явно, что ожидается последовательность элементов.
Что такое std::span
std::span<T> — это невладеющее представление непрерывной последовательности элементов типа T. Обычно внутри он хранит указатель на первый элемент и количество элементов. Для использования нужен заголовок <span>.
Главная идея: функция принимает не конкретно std::vector или C-массив, а любой непрерывный набор данных подходящего типа.
Базовый пример: одна функция для vector, array и C-массива
#include <array>
#include <iostream>
#include <span>
#include <vector>
int sum(std::span<const int> values) {
int result = 0;
for (int value : values) {
result += value;
}
return result;
}
int main() {
int cArray[] = {1, 2, 3};
std::array<int, 3> stdArray = {4, 5, 6};
std::vector<int> vector = {7, 8, 9};
std::cout << sum(cArray) << std::endl;
std::cout << sum(stdArray) << std::endl;
std::cout << sum(vector) << std::endl;
}
Обратите внимание на std::span<const int>. Это значит: функция может читать элементы, но не может их изменять. Для большинства функций анализа, поиска, подсчёта и вывода это лучший вариант.
Когда нужен span без const
Если функция должна менять элементы, используйте std::span<int>. Например, обнулим отрицательные значения:
#include <iostream>
#include <span>
#include <vector>
void replaceNegativeWithZero(std::span<int> values) {
for (int& value : values) {
if (value < 0) {
value = 0;
}
}
}
int main() {
std::vector<int> numbers = {10, -3, 5, -1};
replaceNegativeWithZero(numbers);
for (int value : numbers) {
std::cout << value << ' ';
}
}
Здесь данные изменяются прямо внутри numbers, потому что span не копирует элементы, а ссылается на существующую память.
Передача части массива: first, last и subspan
std::span удобен, когда нужно работать не со всем контейнером, а с его фрагментом.
#include <iostream>
#include <span>
#include <vector>
void print(std::span<const int> values) {
for (int value : values) {
std::cout << value << ' ';
}
std::cout << std::endl;
}
int main() {
std::vector<int> numbers = {10, 20, 30, 40, 50};
std::span<int> view(numbers);
print(view.first(3)); // 10 20 30
print(view.last(2)); // 40 50
print(view.subspan(1, 3)); // 20 30 40
}
Это часто чище, чем передавать индексы начала и конца в каждую функцию.
std::span против const std::vector&
Новички часто пишут функции так:
void print(const std::vector<int>& values);
Это нормально, если функция действительно должна работать только с std::vector. Но если ей достаточно просто пройтись по числам, лучше так:
void print(std::span<const int> values);
Второй вариант принимает vector, array, обычный массив и поддиапазоны. Интерфейс становится универсальнее без шаблонов и лишнего копирования.
Статический размер span
Обычно используют динамический размер: std::span<int>. Но можно указать размер на этапе компиляции:
#include <iostream>
#include <span>
void printRgb(std::span<const int, 3> rgb) {
std::cout << "R=" << rgb[0] << std::endl;
std::cout << "G=" << rgb[1] << std::endl;
std::cout << "B=" << rgb[2] << std::endl;
}
int main() {
int color[] = {255, 128, 0};
printRgb(color);
}
std::span<const int, 3> говорит: функция ожидает ровно 3 элемента. Это полезно для координат, RGB-цветов, небольших фиксированных буферов.
Важные ошибки и ограничения
- std::span не владеет данными. Он не продлевает жизнь массива или вектора.
- Нельзя возвращать span на локальный контейнер. После выхода из функции данные будут уничтожены.
- operator[] не проверяет границы. Всегда проверяйте
size(), если индекс может быть неправильным. - span работает с непрерывной памятью. Он подходит для
vector,array, C-массивов, но не дляstd::listилиstd::map.
Плохой пример:
#include <span>
#include <vector>
std::span<int> makeSpan() {
std::vector<int> temp = {1, 2, 3};
return temp; // Ошибка проектирования: temp скоро будет уничтожен
}
Такой span будет указывать на уже несуществующие данные. Возвращайте сам контейнер или храните данные снаружи.
Практические рекомендации
- Для чтения используйте
std::span<const T>. - Для изменения используйте
std::span<T>. - Не сохраняйте
spanнадолго в полях класса, если не контролируете время жизни данных. - Используйте
subspan(), чтобы явно передавать часть данных вместо пары индексов. - Не применяйте
span, если функция должна владеть данными. Тогда нуженstd::vector,std::arrayили другой контейнер.
Мини-пример из реальной задачи
Допустим, нужно посчитать среднее значение только по части измерений:
#include <iostream>
#include <span>
#include <vector>
double average(std::span<const double> values) {
if (values.empty()) {
return 0.0;
}
double sum = 0.0;
for (double value : values) {
sum += value;
}
return sum / values.size();
}
int main() {
std::vector<double> temperatures = {21.5, 22.0, 23.1, 24.0, 22.8};
std::span<double> all(temperatures);
std::cout << "Average: " << average(all.subspan(1, 3)) << std::endl;
}
Функция average не знает, откуда пришли данные: из вектора, массива или среза. Ей это и не нужно.
Итог
std::span в C++20 — хороший инструмент для функций, которые работают с последовательностью элементов, но не должны владеть памятью. Он делает код универсальнее, безопаснее по смыслу и понятнее, чем связка «указатель + размер».
Если вы хотите системно разобраться не только со std::span, но и с современными возможностями языка, контейнерами, памятью и ООП, посмотрите курс «C++ с нуля до уверенного уровня: практика, задачи и современный подход» — он хорошо ложится на путь от базового синтаксиса к реальной разработке.
-
Создано 14.10.2026 17:00:45
-
Михаил Русаков

Комментарии (0):
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.