Введение
Передача «массива и его размера» в функцию — одна из самых старых и самых проблемных идиом C++: void process(int* data, size_t size). Она не несёт информации о том, кто владеет памятью, не защищает от выхода за границы, заставляет дублировать код для std::vector, std::array, C-массивов и сырых указателей. C++20 вводит std::span<T> — невладеющий, легковесный «вид» на непрерывный массив элементов любого происхождения.
Концепция
std::span<T> хранит указатель на начало данных и их размер (либо размер фиксируется на этапе компиляции, если использовать std::span<T, N>). Он не владеет памятью — не выделяет и не освобождает её, а просто предоставляет единый интерфейс (operator[], begin()/end(), size(), subspan()) для любого непрерывного диапазона: C-массива, std::vector, std::array, части другого span.
Главное преимущество — функция, принимающая std::span<T>, может работать с любым из перечисленных источников без перегрузок и без копирования данных, при этом сохраняя информацию о границах для дополнительных проверок (в debug-сборках многие реализации STL добавляют проверку выхода за границы).
Пример кода
#include <span>
#include <vector>
#include <array>
#include <iostream>
#include <numeric>
// Единая функция работает с любым непрерывным контейнером
double average(std::span<const int> values) {
if (values.empty()) return 0.0;
long long sum = std::accumulate(values.begin(), values.end(), 0LL);
return static_cast<double>(sum) / values.size();
}
void printFirstN(std::span<const int> values, size_t n) {
auto sub = values.subspan(0, std::min(n, values.size()));
for (int v : sub) std::cout << v << " ";
std::cout << "n";
}
void scaleInPlace(std::span<int> values, int factor) {
for (auto& v : values) v *= factor;
}
int main() {
int cArray[] = {1, 2, 3, 4, 5};
std::vector<int> vec = {10, 20, 30, 40};
std::array<int, 3> arr = {7, 8, 9};
std::cout << "Average (C array): " << average(cArray) << "n";
std::cout << "Average (vector): " << average(vec) << "n";
std::cout << "Average (array): " << average(arr) << "n";
printFirstN(vec, 2);
// Изменение "на месте" без копирования: span<int> неконстантный
scaleInPlace(vec, 2);
for (int v : vec) std::cout << v << " ";
std::cout << "n";
// span на часть массива
std::span<int> wholeVec(vec);
std::span<int> middle = wholeVec.subspan(1, 2); // элементы [1..2]
for (int v : middle) std::cout << v << " ";
std::cout << "n";
return 0;
}
Пояснения к коду
average принимает std::span<const int> — это означает «только для чтения вид на непрерывный массив int«. Благодаря неявным конструкторам span из C-массива, std::vector и std::array, одна и та же функция вызывается с тремя совершенно разными типами без единой перегрузки и без копирования данных в новый буфер. Если бы функция принимала const std::vector<int>&, вызвать её с C-массивом или std::array напрямую было бы невозможно без создания временного вектора (то есть с копированием).
scaleInPlace принимает неконстантный std::span<int> и модифицирует исходные данные на месте — span лишь «смотрит» на чужую память, поэтому изменения через него видны в оригинальном контейнере.
subspan(offset, count) создаёт новый span, который указывает на подмножество исходных данных — без копирования, просто пересчитывая указатель и размер.
Подводные камни и советы
1. span не владеет памятью — если исходный контейнер (например, локальный std::vector) выйдет из области видимости или будет изменён так, что инвалидирует указатели (push_back, вызывающий реаллокацию), span, созданный из него, станет «висячим». Никогда не возвращайте span на локальный временный контейнер. 2. Не путайте span<T> и span<const T> — первый разрешает модификацию данных, второй — только чтение; выбирайте константность осознанно в сигнатурах функций. 3. span — это C++20, для более старых стандартов есть аналоги: gsl::span из Guideline Support Library или собственная мини-реализация. 4. Используйте span в сигнатурах функций как более общую и безопасную замену пар (T* data, size_t size) — это уменьшает число перегрузок и явно документирует, что функция не владеет переданными данными. 5. span сам по себе не проверяет границы при operator[] в release-сборке (поведение как у обычного индексирования массива) — для безопасного доступа используйте at()-подобные обёртки вручную, если такой метод доступен в вашей реализации стандартной библиотеки, либо явно проверяйте index < span.size().
================================================================================