make_unique and perfect forwarding

C++C++11Variadic TemplatesUnique PtrPerfect Forwarding

C++ Problem Overview


Why is there no std::make_unique function template in the standard C++11 library? I find

std::unique_ptr<SomeUserDefinedType> p(new SomeUserDefinedType(1, 2, 3));

a bit verbose. Wouldn't the following be much nicer?

auto p = std::make_unique<SomeUserDefinedType>(1, 2, 3);

This hides the new nicely and only mentions the type once.

Anyway, here is my attempt at an implementation of make_unique:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
	return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

It took me quite a while to get the std::forward stuff to compile, but I'm not sure if it's correct. Is it? What exactly does std::forward<Args>(args)... mean? What does the compiler make of that?

C++ Solutions


Solution 1 - C++

Herb Sutter, chair of the C++ standardization committee, writes on his blog:

> That C++11 doesn’t include make_unique is partly an oversight, and it will almost certainly be added in the future.

He also gives an implementation that is identical with the one given by the OP.

Edit: std::make_unique now is part of C++14.

Solution 2 - C++

Nice, but Stephan T. Lavavej (better known as STL) has a better solution for make_unique, which works correctly for the array version.

#include <memory>
#include <type_traits>
#include <utility>

template <typename T, typename... Args>
std::unique_ptr<T> make_unique_helper(std::false_type, Args&&... args) {
  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

template <typename T, typename... Args>
std::unique_ptr<T> make_unique_helper(std::true_type, Args&&... args) {
   static_assert(std::extent<T>::value == 0,
       "make_unique<T[N]>() is forbidden, please use make_unique<T[]>().");

   typedef typename std::remove_extent<T>::type U;
   return std::unique_ptr<T>(new U[sizeof...(Args)]{std::forward<Args>(args)...});
}

template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
   return make_unique_helper<T>(std::is_array<T>(), std::forward<Args>(args)...);
}

This can be seen on his Core C++ 6 video.

An updated version of STL's version of make_unique is now available as N3656. This version got adopted into draft C++14.

Solution 3 - C++

While nothing stops you from writing your own helper, I believe that the main reason for providing make_shared<T> in the library is that it actually creates a different internal type of shared pointer than shared_ptr<T>(new T), which is differently allocated, and there's no way to achieve this without the dedicated helper.

Your make_unique wrapper on the other hand is mere syntactic sugar around a new expression, so while it might look pleasing to the eye, it doesn't bring anything new to the table. Correction: this isn't in fact true: Having a function call to wrap the new expression provides exception safety, for example in the case where you call a function void f(std::unique_ptr<A> &&, std::unique_ptr<B> &&). Having two raw news that are unsequenced with respect to one another means that if one new expression fails with an exception, the other may leak resources. As for why there's no make_unique in the standard: It was just forgotten. (This happens occasionally. There's also no global std::cbegin in the standard even though there should be one.)

Also note that unique_ptr takes a second template parameter which you should somehow allow for; this is different from shared_ptr, which uses type erasure to store custom deleters without making them part of the type.

Solution 4 - C++

std::make_shared isn't just shorthand for std::shared_ptr<Type> ptr(new Type(...));. It does something that you cannot do without it.

In order to do its job, std::shared_ptr must allocate a tracking block in addition to holding the storage for the actual pointer. However, because std::make_shared allocates the actual object, it is possible that std::make_shared allocates both the object and the tracking block in the same block of memory.

So while std::shared_ptr<Type> ptr = new Type(...); would be two memory allocations (one for the new, one in the std::shared_ptr tracking block), std::make_shared<Type>(...) would allocate one block of memory.

That is important for many potential users of std::shared_ptr. The only thing a std::make_unique would do is be slightly more convenient. Nothing more than that.

Solution 5 - C++

In C++11 ... is used (in template code) for "pack expansion" too.

The requirement is that you use it as a suffix of an expression containing an unexpanded pack of parameters, and it will simply apply the expression to each of the elements of the pack.

For example, building on your example:

std::forward<Args>(args)... -> std::forward<int>(1), std::forward<int>(2),
                                                     std::forward<int>(3)

std::forward<Args...>(args...) -> std::forward<int, int, int>(1,2,3)

The latter being incorrect I think.

Also, pack of arguments may not be passed to a function unexpanded. I am unsure about a pack of template parameters.

Solution 6 - C++

Inspired by the implementation by Stephan T. Lavavej, I thought it might be nice to have a make_unique that supported array extents, it's on github and I'd love to get comments on it. It allows you to do this:

// create unique_ptr to an array of 100 integers
auto a = make_unique<int[100]>();

// create a unique_ptr to an array of 100 integers and
// set the first three elements to 1,2,3
auto b = make_unique<int[100]>(1,2,3); 

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionfredoverflowView Question on Stackoverflow
Solution 1 - C++Johan RådeView Answer on Stackoverflow
Solution 2 - C++tominatorView Answer on Stackoverflow
Solution 3 - C++Kerrek SBView Answer on Stackoverflow
Solution 4 - C++Nicol BolasView Answer on Stackoverflow
Solution 5 - C++Matthieu M.View Answer on Stackoverflow
Solution 6 - C++Nathan BinkertView Answer on Stackoverflow