шаблоны и библиотекаtemplates and library
Фабричная функция для unique_ptr, устраняющая риск утечки при new T(...) напрямую в аргументе — появилась только в C++14, хотя её аналог для shared_ptr, make_shared, был в стандартной библиотеке уже с C++11.A factory function for unique_ptr that eliminates the leak risk of writing new T(...) directly in an argument — it only arrived in C++14, even though its shared_ptr counterpart, make_shared, had been in the standard library since C++11.
std::make_unique<T>(args...) — шаблонная функция, которая конструирует объект T с аргументами args... и оборачивает его в std::unique_ptr<T> за один выразительный вызов. Функционально она почти эквивалентна std::unique_ptr<T>(new T(args...)), но безопаснее в присутствии исключений — по причинам, разобранным в разделе «Как это работает под капотом».std::make_unique<T>(args...) is a template function that constructs a T object with arguments args... and wraps it in a std::unique_ptr<T> in one expressive call. Functionally it's almost equivalent to std::unique_ptr<T>(new T(args...)), but safer in the presence of exceptions — for reasons covered in "How it works under the hood."
auto w = std::unique_ptr<Widget>(new Widget(42));
// работает, но небезопасно в составе списка аргументов —// works, but unsafe as part of an argument list —
// см. секцию "Грабли"// see the "Gotchas" section
auto w = std::make_unique<Widget>(42);
// короче и исключительно-безопасно в любом контексте// shorter and exception-safe in any context
Отсутствие make_unique в C++11 — не осознанное архитектурное решение, а признанный самим комитетом недосмотр:The absence of make_unique in C++11 wasn't a deliberate design decision — it was an oversight the committee itself acknowledged:
std::make_shared<T>(args...) появился в стандартной библиотеке вместе с shared_ptr ещё в C++11 и сразу давал дополнительный бонус — совмещение аллокации control block и объекта в одном выделении памяти. Для unique_ptr, у которого control block нет, аналогичной фабрики не было вовсе, хотя мотивация (исключительная безопасность, компактность вызова) была ровно та же.std::make_shared<T>(args...) appeared in the standard library alongside shared_ptr back in C++11 and came with an extra bonus — combining the control block and object allocation into a single memory allocation. For unique_ptr, which has no control block, no equivalent factory existed at all, even though the motivation (exception safety, a compact call) was exactly the same.
Herb Sutter в предложении N3588 прямо указал на этот пробел как на недосмотр при финализации C++11: у комитета просто не дошли руки добавить make_unique вовремя, при том что все обоснования для него уже существовали и активно использовались для make_shared. N3588 был принят, и make_unique вошёл в C++14 — фактически заплатка для пропуска в предыдущем стандарте, а не новая концепция.Herb Sutter, in proposal N3588, explicitly pointed out this gap as an oversight during C++11's finalization: the committee simply didn't get around to adding make_unique in time, even though all the justifications for it already existed and were actively used for make_shared. N3588 was accepted, and make_unique made it into C++14 — effectively a patch for a gap in the previous standard, not a new concept.
Именно из-за отсутствия стандартной фабрики программисты были вынуждены писать f(std::unique_ptr<T>(new T()), g()) — конструкцию, уязвимую к утечке, если компилятор вычислит new T(), затем g() (которая бросает исключение), и только потом сконструирует сам unique_ptr. До C++17, где порядок вычисления аргументов в подобных случаях был ужесточён, эта уязвимость была реальной проблемой на практике, а не теоретической.Precisely because no standard factory existed, programmers were forced to write f(std::unique_ptr<T>(new T()), g()) — a construct vulnerable to a leak if the compiler evaluates new T(), then g() (which throws), and only afterward constructs the unique_ptr itself. Before C++17, which tightened argument evaluation order in cases like this, this vulnerability was a real practical problem, not a theoretical one.
make_unique устраняет опасность тем, что вся работа — выделение памяти оператором new, конструирование объекта и оборачивание результата в unique_ptr — происходит внутри одного вызова функции, до того как управление вернётся к вызывающему коду и продолжится вычисление остальных аргументов внешнего выражения. Раз new T(args...) и обёртка в unique_ptr — это одно неделимое с точки зрения вызывающего кода действие, между ними не может вклиниться исключение из соседнего аргумента.make_unique removes the danger because all the work — allocating memory with new, constructing the object, and wrapping the result in a unique_ptr — happens inside a single function call, before control returns to the calling code and evaluation of the outer expression's remaining arguments continues. Since new T(args...) and the unique_ptr wrapping are, from the caller's perspective, one indivisible action, an exception from a sibling argument can't wedge itself in between.
// упрощённая (но по сути верная) реализация make_unique для единичного объекта:// simplified (but essentially correct) implementation of make_unique for a single object:
template <class T, class... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
// использование — неотличимо от вызова конструктора T напрямую:// usage — indistinguishable from calling T's constructor directly:
auto w = make_unique<Widget>(42, "label");
Обратите внимание: эта реализация опирается на variadic templates и perfect forwarding — оба механизма из C++11, и именно они делают возможным написать такую функцию самостоятельно, если приходится работать в C++11-окружении (см. cpp11/unique-ptr.html, где этот приём упомянут как обходной путь, и cpp11/variadic-templates.html для деталей механизма). Собственно C++14 не добавляет здесь ничего нового в язык — make_unique целиком реализуем средствами C++11, просто библиотека не включала его до C++14. Единственное отличие полной реализации стандартной библиотеки от приведённой — дополнительная перегрузка для массивов (make_unique<T[]>(size)) и явный запрет вызова с указанным размером массива известной длины на этапе компиляции.Note that this implementation relies on variadic templates and perfect forwarding — both C++11 mechanisms, and they're exactly what makes it possible to write such a function yourself if you have to work in a C++11 environment (see cpp11/unique-ptr.html, where this technique is mentioned as a workaround, and cpp11/variadic-templates.html for the mechanism's details). C++14 itself adds nothing new to the language here — make_unique is entirely implementable with C++11 facilities; the library simply didn't include it until C++14. The only difference between the full standard-library implementation and the one shown here is an additional overload for arrays (make_unique<T[]>(size)) and an explicit compile-time rejection of calls specifying a size for an array of already-known length.
f(std::unique_ptr<T>(new T()), g()) остаётся небезопасным паттерном, если по привычке или в legacy-коде продолжать писать new вручную вместо make_unique — стандарт C++14 сам по себе не запрещает старый синтаксис, он лишь даёт безопасную альтернативу, которой нужно пользоваться сознательно.f(std::unique_ptr<T>(new T()), g()) remains an unsafe pattern if, out of habit or in legacy code, you keep writing new by hand instead of make_unique — the C++14 standard doesn't itself forbid the old syntax, it merely offers a safe alternative that has to be used deliberately.
В отличие от прямого конструктора unique_ptr<T, Deleter>(ptr, deleter), у make_unique нет перегрузки, принимающей deleter — если нужен нестандартный способ освобождения ресурса (например, fclose вместо delete), придётся вернуться к явному конструированию unique_ptr напрямую, как показано в cpp11/unique-ptr.html.Unlike the direct constructor unique_ptr<T, Deleter>(ptr, deleter), make_unique has no overload accepting a deleter — if you need a non-standard way to release a resource (say, fclose instead of delete), you'll have to fall back to explicitly constructing the unique_ptr directly, as shown in cpp11/unique-ptr.html.
std::make_unique<int[]>(10) оставляет элементы массива неинициализированными (для примитивных типов) — в отличие от new int[10]() с круглыми скобками, которые зануляют элементы. Ожидание, что make_unique для массива ведёт себя как new T[n](), а не как new T[n], — частая ошибка.std::make_unique<int[]>(10) leaves array elements uninitialized (for primitive types) — unlike new int[10]() with parentheses, which zero-initializes the elements. Expecting make_unique for an array to behave like new T[n]() rather than new T[n] is a common mistake.