make_unique and perfect forwarding
Asked Answered
U

6

227

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?

Unripe answered 12/8, 2011 at 9:46 Comment(7)
Pretty sure we've had this discussion before... also note that unique_ptr takes a second template parameter which you should somehow allow for - that's different from shared_ptr.Pomander
Perhaps it's because it's rather easy to write yourself? While a make_unique() simply wraps a new statement, it's not like make_shared() which is actually not trivial to implement correctly (it allocates the space for the reference count and the object at the same time). But good question though.Sarchet
@Kerrek: I don't think it would make sense to parameterize make_unique with a custom deleter, because obviously it allocates via plain old new and hence must use plain old delete :)Unripe
@Fred: That's true. So, the proposed make_unique would be limited to new allocation... well, it's fine if you want to write it, but I can see why something like that isn't part of the standard.Pomander
Actually, I like to use a make_unique template since the constructor of std::unique_ptr is explicit, and thus it is verbose to return unique_ptr from a function. Also, I'd rather use auto p = make_unique<foo>(bar, baz) than std::unique_ptr<foo> p(new foo(bar, baz)).Chayachayote
As alternative you can write p.reset( new SomeUserDefinedType(1, 2, 3) );Alsatia
make_unique is coming in C++14, see isocpp.org/blog/2013/04/trip-report-iso-c-spring-2013-meetingAntagonistic
S
161

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.

Spiroid answered 11/3, 2012 at 19:6 Comment(12)
A make_unique function template does not itself guarantee exception safe calls. It relies on convention, that the caller uses it. In contrast, strict static type checking (which is the main diff between C++ and C) is built on the idea of enforcing safety, via types. And for that, make_unique can simply be a class instead of function. For example, see my blog article from May 2010. It's also linked to from the discussion on Herb's blog.Gaullist
@Cheersandhth.-Alf - I don't quite follow your comment. How is make_unique not exception safe? How would making it a class make it more exception safe? Inheriting from a class that is not designed to be derived does not seem like a good idea to me at any rate...Chatoyant
@DavidRodríguez-dribeas: read Herb's blog to understand the exception safety issue. forget about "not designed to be derived", that's just rubbish. instead of my suggestion of making make_unique a class, i now think it's better to make it a function that produces a make_unique_t, the reason for that is a problem with the most perverse parse :-).Gaullist
@Cheersandhth.-Alf: Maybe we have read a different article, because the one I just read clearly states that make_unique offers the strong exception guarantee. Or maybe you are mixing the tool with its use, in which case no function is exception safe. Consider void f( int *, int* ){}, clearly offers the no throw guarantee, but by your line of reasoning it is not exception safe, as it can be misused. Worse, void f( int, int ) {} is not exception safe either!: typedef unique_ptr<int> up; f( *up(new int(5)), *up(new int(10)))...Chatoyant
@DavidRodríguez-dribeas: it seems your point is that nothing is exception safe because everything can be abused so that it is involved in some exception unsafe code, and secondly, also that nothing is exception unsafe because it can theoretically be used in exception safe ways, and third, that by the authority of the article you refer to, make_unique magically confers exception safety on code where it can be applied. sorry, you fail to convince me. as i see it none of your three points are valid logic, and they're also a bit mutually contradictory.Gaullist
@David: anyway, i fail to see the utility of not having a more safe to use make_unique function. why are you arguing that it should be more risky?Gaullist
@Cheersandhth.-Alf: I asked about the exception issues in make_unique implemented as above and you point me to an article by Sutter, the article that my google-fu pointed me to states that make_unique offers the strong exception guarantee, which contradicts your statement above. If you have a different article I am interested in reading it. So my original question stands How is make_unique (as defined above) not exception safe? (Sidenote: yes, I do think that make_unique improves exception safety in places where it can be applied)Chatoyant
... also I am not pursuing a less safe to use make_unique function. First I don't see how this one is unsafe, and I don't see how adding an extra type would make it safer. What I do know is that I am interested in understanding the issues that this implementation can have --I cannot see any--, and how an alternative implementation would solve them. What are the conventions that make_unique depends on? How would you use type checking to enforce safety? Those are the two questions for which I would love an answer.Chatoyant
@David: well, ok. first, the exception safety issue is not about make_unique. it is about the natural use of a function declared with two or more unique_ptr args. make_unique is just one tool that a user of such an unsafe function can use to make a call safe. the user can also do that in other ways, but still the function is unsafe in the original way: it requires some deep understanding to write more to make calls safe. instead the formal arguments can be of a type that requires use of make_unique, or some explicit passing of ownership of existing objects. that's far more robust.Gaullist
@Cheersandhth.-Alf: Ok, so what you propose is changing the interface to take a make_unique_t that forces the use of make_unique and internally holds the unique_ptr to ensure that callers will use make_unique which is safe. Initially I understood that you claimed make_unique not to be/make the call exception safe. Regarding this extra type, the problem is that you will then limit the uses of your function to memory that has been just allocated which would limit usability (alternatively provide a make_unique variant that is able to move from an already constructed unique_ptr)Chatoyant
yes, roughly. as i wrote, some "explicit passing of ownership" must be supported. as i didn't write, there's not so much space in these comments, make_unique_ptr_t<T> is most naturally a class derived from unique_ptr<T>, and then it just has a unique_ptr<T> base class sub-object and adds no possible overhead -- i.e., it's gratis.Gaullist
@hyde: The link was to the solution to the Guru of the Week Problem #102, herbsutter.com/2011/12/02/…. The solution seems to have been removed from the site.Buhrstone
H
80

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.

Hollander answered 22/11, 2012 at 12:0 Comment(6)
make_unique is been proposed by Stephen T Lavalej into the next std update.Hollander
Here is a like to him talking about adding it. channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-/…Hollander
why make all the unnecessary edits xeo, it was fine as it was. That code was as exactly as Stephen T. Lavalej put it, and he works for dinkumware which maintains the std library. You've already commented on that wall, so you should know.Hollander
The implementation for make_unique should go in a header. Headers should not import a namespace (see Item #59 in Sutter/Alexandrescu's "C++ Coding Standards" book). Xeo's changes help avoid encouraging bad practices.Hippopotamus
unfortunately, not supported by VC2010, even VC2012 I think, which both not support varidic tempalte parameterMauchi
This implementation is now in VC2013, which has new been released. It will also be included in the latest version of other C++ IDE's that allow C++13.Hollander
P
19

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.

Pomander answered 12/8, 2011 at 9:53 Comment(8)
What exactly do you mean by "different internal type"?Unripe
@FredOverflow: The shared pointer is a relatively complicated class; internally it keeps a polymorphic reference control block, but there are several different kinds of control blocks. shared_ptr<T>(new T) uses one of them, make_shared<T>() uses a different one. Allowing that is a Good Thing, and the make-shared version is in some sense the lightest-weight shared pointer you can get.Pomander
@FredOverflow: shared_ptr allocates a block of dynamic memory to keep up the count and the "disposer" action when you create a shared_ptr. If you pass the pointer explicitly, it needs creating a "new" block, if you use make_shared it can bundle your object and the satellite data in a single block of memory (one new) resulting in faster allocation/deallocation, less fragmentation, and (normally) better cache behavior.Goatherd
I think I need to re-watch shared_ptr and friends...Unripe
@FredOverflow: shared_ptr is the only strange beast, because of this extraneous information it needs to track (count and deleter). The others are, thankfully, much simpler to grok.Goatherd
@Matthieu: Indeed, unique_ptr is a far lighter-weight class -- I would say that make_shared is as close in concept to unique_ptr as you can get, only that it uses an allocator rather than letting the user pick the allocation function.Pomander
-1 "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." is wrong. It brings the possibility of exception safe function invocation. It does not bring a guarantee, however; for that, it would need to be class so that the formal arguments could be declared of that class (Herb described that as the difference between opt-in and opt-out).Gaullist
@Cheersandhth.-Alf: Yeah, that's true. I've since realised this. I'll edit the answer.Pomander
G
19

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.

Ghent answered 12/8, 2011 at 9:55 Comment(2)
It's not required. Hinted, but not required.Hypermeter
It's not only for convenience, it would also improve exception safety in certain cases. See Kerrek SB's answer for that.Untimely
G
13

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.

Goatherd answered 12/8, 2011 at 10:23 Comment(11)
Nice to see this spelt out. Why not include the variadic template parameter, too?Pomander
@Kerrek: because I am not so sure about its strange syntax, and I don't know if many people have played with. So I'll keep to what I know. It may warrant a c++ FAQ entry, if someone was motivated and knowledgeable enough, for the variadic syntax is quite complete.Goatherd
The syntax is std::forward<Args>(args)..., which expands to forward<T1>(x1), forward<T2>(x2), ....Pomander
@Kerrek: Oh, I thought you were talking about pure type manipulations. I don't see the need to put the type here, the T1... would come from nowhere.Goatherd
:I think forward actually always requires a template parameter, doesn't it?Pomander
@Kerrek: I don't think so, ideone seems to agree ideone.com/9cxH6 (but then, I would not take it at face value).Goatherd
Hmmm, I'm not 100% on this, but if you don't specify the parameter, it might be that you're not getting what you intend -- i.e. the parameter might be derived wrongly. It'd still compile, but for instance you might force an rvalue to become an lvalue. I'm not sure though.Pomander
@Matthieu: If you don't specify the template parameter in std::forward, it will not compile. It was intentionally designed to error-out in this way.Putdown
@Howard: thanks! Seems to be a gcc glitch on ideone then, I'll patch my example then :)Goatherd
ideone may not have picked it up because you didn't instantiate make_unique in your example.Putdown
@Howard: right! Forcing instantiation triggers the warning (ideone.com/GDNHb)Goatherd
R
5

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); 
Riti answered 2/1, 2013 at 23:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.