В чём принципиальная опасность привязки временного объекта к ссылочному члену в списке инициализации конструктора?
В современном стандарте C++ инициализация ссылочного члена временным объектом в списке инициализации конструктора считается некорректной. Исторически такая конструкция обычно принималась компиляторами, но временный объект жил только до завершения конструктора, после чего член становился висячей ссылкой.
Ссылочный член хранит только ссылку на существующий объект и не владеет его временем жизни. Для локальной ссылки C++ продлевает время жизни временного объекта до конца области действия ссылки, но это правило не распространяется аналогичным образом на ссылочный член, создаваемый в конструкторе.
Проблема стала настолько существенной, что правило было изменено решением CWG 1696: подобная инициализация должна отвергаться. Однако некоторые компиляторы сохраняют поддержку старого поведения, иногда с предупреждением.
Рассмотрим класс, который хранит ссылку на строку:
В переносимом современном C++ такой код должен быть диагностирован как некорректный. Если компилятор его принимает, временная строка уничтожается после завершения конструктора View, а text продолжает ссылаться на уже уничтоженный объект.
Использование такой ссылки даёт неопределённое поведение: возможны повреждение данных, ошибочные результаты или падение программы. const делает объект доступным только для чтения, но не продлевает его время жизни и не добавляет владения.
Временный объект, созданный в списке инициализации конструктора, существует в контексте выполнения конструктора. После завершения конструктора он уничтожается, если другое правило времени жизни не предусматривает продление. Ссылочный член при этом не превращается в копию объекта и не получает самостоятельного хранилища.
Надёжные варианты зависят от семантики класса:
std::shared_ptr<const T>, если требуется разделяемое владение;std::string_view только тогда, когда время жизни исходной строки гарантировано отдельно.std::string_view не исправляет проблему: это невладеющее представление, аналогичное ссылке по смыслу времени жизни. Передача временной строки в объект, который сохраняет string_view, также может привести к висячему представлению.
Компонент форматирования принимает настройки строкой и сохраняет их для последующих вызовов. Разработчик делает ссылочный член, чтобы избежать копирования, и передаёт временный результат сборки строки. На тестах код может работать, потому что память ещё не перезаписана, но после следующей аллокации содержимое становится повреждённым.
Хранение const std::string& экономит копирование, но требует строгого внешнего контракта времени жизни и не допускает временные аргументы. Хранение std::string_view дешевле по размеру, но имеет ту же проблему владения. Хранение std::string создаёт копию или перемещает значение, зато делает объект самостоятельным и безопасным.
Для компонента, который должен принимать как именованные строки, так и временные результаты, выбирают хранение по значению. Дополнительная копия обычно является приемлемой платой за корректное владение; при необходимости перемещение временного std::string уменьшает издержки.
const-ссылка в члене время жизни временного объекта?Нет. const определяет доступ только для чтения и может участвовать в некоторых правилах привязки ссылок, но не превращает ссылочный член во владеющий объект. Время жизни определяется контекстом инициализации, а не наличием const.
Да. Для агрегата, инициализированного фигурными скобками, временный объект, привязанный к ссылочному элементу, обычно получает время жизни агрегата. При инициализации агрегата круглым списком выражений временный объект живёт только до завершения полного выражения, содержащего инициализацию. Это специальное различие C++20, поэтому заменять фигурные скобки круглыми без проверки семантики опасно.
Во время выполнения вызова — да: временный объект, привязанный к ссылочному параметру, живёт до завершения полного выражения, содержащего вызов функции. Но если функция сохраняет адрес или ссылку в поле другого объекта, после завершения вызова сохранённая ссылка может стать висячей.
Следовательно, константная ссылка безопасна для непосредственного использования внутри функции, но не гарантирует безопасность последующего хранения ссылки. Для длительного хранения нужно копировать значение или явно организовать владение объектом.