Объясните, почему std::span не продлевает время жизни представляемого диапазона.
std::span — это невладеющее представление диапазона: он хранит адрес начала и размер, но не управляет объектами и не продлевает их время жизни. Если исходный массив или контейнер уничтожен, перемещён либо изменил размещение элементов, ранее созданный std::span может стать недействительным.
В C++ часто требуется передать функции непрерывный диапазон без копирования элементов и без передачи владения. Для этого применялись пары «указатель плюс размер», ссылки на контейнеры и специализированные типы представлений.
std::span, стандартизованный в C++20, формализует такое невладеющее представление. Подход позволяет отделить работу с диапазоном от управления его памятью, но сознательно возлагает контроль времени жизни на вызывающий код.
Функция может получить std::span от локального массива, временного контейнера или контейнера, который затем перераспределяет память. Сам объект span при этом продолжит существовать, но его адрес и размер уже не гарантируют доступ к живым элементам.
Обращение к такому представлению приводит к неопределённому поведению. Ошибка особенно опасна тем, что span внешне похож на безопасный контейнер, хотя владельцем данных не является.
std::span<T> обычно содержит указатель на первый элемент и количество элементов. Копирование span копирует только это представление: элементы не копируются, не выделяется память и никакой счётчик владельцев не изменяется.
После выхода из view локальный vector уничтожен вместе со своими элементами. Возвращённый span содержит адрес уже завершивших время жизни объектов, поэтому он не становится безопасным только из-за возвращения по значению.
Даже при живом контейнере представление может устареть после операции, вызывающей перераспределение памяти, например после увеличения ёмкости std::vector. В этом случае нужно создать новый span после завершения такой операции.
Безопасная модель такова: владелец данных должен жить дольше каждого использующего их span, а операции, способные инвалидировать адрес элементов, не должны выполняться до окончания использования представления. Если функция должна владеть данными или гарантировать их жизнь, следует передавать либо копию, либо владеющий объект, например std::vector или подходящий умный указатель.
std::span<const T> запрещает изменение элементов через представление, но не делает их временем жизни управляемыми. Константность доступа и владение ресурсом — независимые свойства.
Сервис принимает большой буфер для разбора сетевого сообщения. Вариант с передачей std::vector<std::byte> по значению обеспечивает владение и время жизни, но может привести к копированию. Передача const std::vector<std::byte>& избегает копирования, однако связывает интерфейс с конкретным контейнером.
Вариант с std::span<const std::byte> принимает массивы и разные непрерывные контейнеры без копирования. Он выбран, потому что буфер гарантированно живёт до завершения синхронного разбора, а интерфейсу не требуется владение; для асинхронной обработки сначала создаётся собственная копия данных.
Результат — универсальный и эффективный синхронный интерфейс без скрытого выделения памяти. Для отложенного использования отдельный владеющий объект устраняет риск обращения к истёкшему представлению.
Продлевает ли std::span<const T> время жизни временного контейнера?
Нет. const ограничивает запись через представление, но не добавляет владения и не влияет на время жизни контейнера. Если временный контейнер уничтожается сразу после формирования выражения, span не может сохранить его элементы живыми.
Становится ли span недействительным при изменении значения элементов?
Само изменение значений обычно не инвалидирует представление: адреса и время жизни элементов остаются прежними. Однако операции, которые перемещают элементы, уничтожают их или меняют размещение хранилища, например перераспределение std::vector, могут сделать сохранённый span недействительным.
Можно ли безопасно вернуть std::span из функции, если данные созданы внутри неё?
Только если функция возвращает представление на данные, чья жизнь управляется внешним владельцем или статическим хранилищем. Представление на локальный массив, локальный контейнер или другой локальный объект после выхода из функции недействительно, поскольку время жизни исходных элементов уже завершилось.