Разберите последствия создания временного RAII-объекта без сохранения его в переменной.
Временный RAII-объект обычно уничтожается в конце полного выражения, то есть, как правило, уже в конце строки с точкой с запятой. Поэтому ресурс может быть освобождён сразу после создания объекта, а не в конце нужного блока. Для ресурсов, чьё действие должно охватывать несколько операций, RAII-объект следует сохранить в именованной переменной или явно ограничить его область видимости.
RAII связывает получение ресурса с конструктором, а освобождение — с деструктором. Такой подход решает проблему ручного освобождения ресурсов при обычном выходе из функции, раннем return и исключениях: уничтожение локальных объектов выполняется автоматически при выходе из области видимости.
Однако автоматическое уничтожение работает по правилам времени жизни самого объекта. RAII не означает, что ресурс живёт до конца блока независимо от формы создания объекта: если объект временный, его время жизни может оказаться значительно короче ожидаемого.
Предположим, RAII-объект захватывает блокировку mutex, открывает файл или начинает транзакцию. Если создать его как временный объект и не сохранить, деструктор может выполниться сразу после завершения выражения.
В результате блокировка будет снята до выполнения следующей операции, файл закрыт раньше времени, а транзакция завершена до фактического изменения данных. Код при этом может выглядеть корректно и успешно компилироваться, что делает ошибку особенно опасной.
Временный объект, созданный внутри выражения, живёт до конца полного выражения. В обычном операторе это означает момент после вычисления выражения и до перехода к следующему оператору.
В первом случае конструктор std::lock_guard захватывает mutex, а его деструктор вызывается сразу после завершения полного выражения. Вторая функция сохраняет объект в переменной guard, поэтому деструктор вызывается при выходе из функции, в том числе при исключении.
Надёжный шаблон — давать RAII-объекту имя. Если ресурс должен действовать только на часть функции, можно создать отдельный вложенный блок: тогда именованный объект уничтожится при выходе именно из этого блока.
Создание временного RAII-объекта не всегда ошибочно. Оно корректно, если ресурс должен существовать только в пределах одного выражения, например при передаче объекта в функцию, которая полностью использует ресурс во время вызова. Но такое намерение должно быть очевидным, иначе именованная переменная обычно безопаснее и понятнее.
Привязка временного объекта к локальной ссылке может продлить его время жизни до времени жизни ссылки, но это усложняет чтение и создаёт дополнительные правила. Для обычного управления ресурсом предпочтительнее явно назвать объект.
В многопоточном сервисе разработчик добавил временный std::lock_guard перед обновлением общего состояния. Тесты на одном потоке проходили, но при нагрузке возникали гонки: mutex освобождался в конце строки, а не после завершения обновления.
Рассматривались два варианта. Можно было оставить временный объект и надеяться на визуальный порядок операций, но это не удерживало блокировку и делало код хрупким. Можно было использовать именованный std::lock_guard; этот вариант не требует ручного unlock, корректно работает при исключениях и явно показывает границу владения блокировкой.
Выбрали именованный объект в минимальном вложенном блоке. В результате mutex удерживался ровно во время критической секции, а его освобождение оставалось автоматическим при любом выходе из блока.
1. Всегда ли временный RAII-объект уничтожается в конце функции?
Нет. Его обычное время жизни заканчивается в конце полного выражения, а не в конце окружающего блока. Поэтому временный объект в отдельной строке обычно уничтожается раньше, чем функция продолжит выполнение.
Исключения из общего впечатления возможны, если время жизни временного объекта продлено специальным правилом, например привязкой к локальной ссылке. Но передача временного объекта в функцию сама по себе не продлевает его жизнь после завершения полного выражения с вызовом.
2. Продлевает ли auto&&, получившая временный RAII-объект, его время жизни?
При инициализации локальной rvalue-ссылки временный объект обычно живёт столько же, сколько эта ссылка. Поэтому локальная auto&& может удерживать ресурс дольше одной строки.
Тем не менее такой приём хуже именованного объекта по читаемости: тип и намерение владения становятся менее очевидными, а поведение auto&& зависит от категории выражения. Для RAII-ресурсов следует предпочитать обычную локальную переменную с понятным именем.
3. Что произойдёт, если временный guard передать в функцию?
Guard будет существовать во время вычисления аргументов и выполнения вызова функции, если функция получает его по ссылке или использует связанный с ним ресурс. После завершения полного выражения с вызовом временный объект уничтожится, поэтому ресурс не будет удерживаться на последующих строках.
Это может быть намеренно полезно для операции, полностью выполняющейся внутри вызова. Но если вызывающий код рассчитывает на сохранение блокировки или другой гарантии после возврата функции, временный объект нужно сохранить в локальной переменной.