Почему std::make_unique безопаснее прямой передачи результата new в функцию с несколькими аргументами?
std::make_unique сразу связывает созданный объект с std::unique_ptr, поэтому между выделением памяти и установлением владения нет доступного для исключения промежутка. При передаче необёрнутого результата new в функцию исключение при вычислении другого аргумента может оставить объект без владельца и вызвать утечку.
Ручное управление памятью требует отдельно создать объект, определить владельца и гарантировать вызов delete на всех путях выхода. Особенно трудно это обеспечить при исключениях, когда обычный линейный порядок выполнения прерывается.
RAII решает исходную проблему тем, что владение ресурсом представляется объектом с деструктором. std::make_unique объединяет выделение памяти, создание объекта и передачу владения так, чтобы ресурс с самого начала находился под контролем RAII-объекта.
Рассмотрим функцию, принимающую несколько аргументов. Если один аргумент создаётся через необёрнутый new, а вычисление другого аргумента выбрасывает исключение, управление может не дойти до места, где созданный объект должен быть передан владельцу.
Последствие — утечка памяти. Даже если конкретная форма записи с явным созданием unique_ptr безопасна в данной версии стандарта и при данном deleter, она сложнее для анализа и легче становится ошибочной после изменения интерфейса или пользовательского deleter.
std::make_unique<T>(аргументы) выполняет создание объекта и возвращает уже готовый std::unique_ptr<T>. Если конструктор объекта выбрасывает исключение, память корректно освобождается механизмом выделения; если создание успешно, временный smart pointer немедленно становится владельцем ресурса.
Минимальный пример:
Если calculate() выбросит исключение, временный std::unique_ptr, уже созданный make_unique, будет уничтожен при раскрутке стека и освободит Widget. Владелец не представлен сырым указателем ни на одном наблюдаемом этапе.
Важно не утверждать, что всякая запись с std::unique_ptr<T>(new T(...)) автоматически небезопасна. В простой отдельной инициализации она обычно корректна, однако make_unique делает границу владения явной и уменьшает зависимость от порядка вычисления аргументов, исключений и особенностей deleter.
Есть и практические ограничения. make_unique не продлевает жизнь объектов, переданных конструктору по сырым указателям, и не делает сам объект потокобезопасным. Для массивов применяется форма std::make_unique<T[]>; для ресурсов, освобождаемых не через delete, нужен подходящий deleter и соответствующая конструкция владения.
Сервис создаёт объект конфигурации и передаёт его вместе с результатом загрузки метаданных. В старом варианте объект сначала создавался через new, а затем передавался как сырой указатель; при исключении загрузки метаданных память утекала.
Возможные варианты:
Выбран последний вариант. Объект сразу получает владельца, исключения автоматически запускают уничтожение локального unique_ptr, а сигнатура функции явно показывает передачу владения. В результате исчезла утечка на аварийном пути и не потребовался ручной код очистки.
1. Всегда ли выражение std::unique_ptr<T>(new T(...)) приводит к утечке при исключении другого аргумента?
Нет. Нельзя делать безусловный вывод, что любая такая запись небезопасна: значение имеет версия стандарта, порядок вычисления и момент, когда временный unique_ptr уже создан. Однако прямой new концептуально сначала создаёт ресурс без владельца, поэтому такая форма хуже масштабируется и менее явно выражает безопасную границу владения. make_unique предпочтительнее как стандартный способ атомарно получить объект под управлением unique_ptr.
2. Что произойдёт, если конструктор объекта, вызываемый внутри make_unique, выбросит исключение?
Объект считается не созданным, а выделенная для него память освобождается. Возвращаемый unique_ptr не появляется, поэтому владение не нужно вручную передавать или отзывать. Это один из ключевых эффектов RAII: частично выполненное создание не оставляет ресурс без ответственного владельца.
3. Почему make_unique не заменяет передачу сырого указателя в невладеющий интерфейс?
Потому что make_unique создаёт владельца, но не меняет контракт функции. Если функция только временно использует объект и не должна владеть им, ей следует передавать ссылку или сырой указатель как невладеющее представление, при этом владелец должен жить дольше вызова. Передача unique_ptr по значению означает передачу владения, а передача по ссылке — иное соглашение и не должна ошибочно интерпретироваться как передача ресурса.