A note on make_unique<T>(Args&&...)
Since C++14, unique_ptr<T>
has been accpompanied by the factory function make_unique<T>(Args&&...)
that perfectly forwards its arguments to the constructor of T
. Why standard library implementors provide a separate factory function make_unique<T>(Args&&...)
, when the constructor unique_ptr<T>(T*)
does the same job?
std::unique_ptr<T>
models ownership of the resource semantics. Calling unique_ptr<T>(T*)
makes the client code responsible for supplying a pre-existing T
object whose address is passed as an argument.
Consider the following code snippet:
#include<iostream>
#include<memory>
template<typename T>
class pair_allocator{
private:
std::unique_ptr<T> p1;
std::unique_ptr<T> p2;
public:
pair_allocator() = default;
pair_allocator(T x, T y)
: p1(new T(x))
, p2(new T(y))
{}
~pair_allocator() = default;
};
We know that, the member subobjects of a C++ object are constructed in the order of their declaration. So, p1
is constructed before p2
. Also, the allocation and construction operation new T(x)
precedes the construction of p1
. new T(y)
precedes the construction of p2
.
Denoting \(A:=\) new T(x)
, \(B:=\) Construction of p1
, \(C:=\) new T(y)
, \(D:=\) Construction of p2
.
If we see the rules laid out above, we could have the operations in the following order: \(A \rightarrow B \rightarrow C \rightarrow D\), but we could also have \(A \rightarrow C \rightarrow B \rightarrow D\) or \(C \rightarrow A \rightarrow B \rightarrow D\), in which case the two calls to new T(...)
occur prior to the construction of p1
and p2
. If this happens, then an exception thrown by the second call to new T(...)
would lead to a memory leak, because we fail to release the memory allocated by the first call to new T()
.
The factory function make_unique<T>(Args&&...)
is a wrapper over the operations new T()
and unique__ptr<T>()
, and so if the second call to new T()
fails, the object p1
goes out of scope, its destructor ~unique_ptr<T>()
in turn calls operator delete T
, destroying the T
object and releasing the memory held by T
.
If we modify the above snippet as:
#include<iostream>
#include<memory>
template<typename T>
class pair_allocator{
private:
std::unique_ptr<T> p1;
std::unique_ptr<T> p2;
public:
pair_allocator() = default;
pair_allocator(T x, T y)
: p1(make_unique<T>(x))
, p2(make_unique<T>(y))
{}
~pair_allocator() = default;
};
In this instance, the client code will never find itself with floating results from calls to new
. make_unique<T>
is therefore a security feature that prevents client code being exposed to ownerless resources.