ChatGPT解决这个技术问题 Extra ChatGPT

make_unique and perfect forwarding

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?

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.
@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 :)
@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.
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)).

I
Ivan Perevezentsev

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.


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.
@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 :-).
@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)))...
@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)
... 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.
B
Bruno De Fraine

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.


make_unique is been proposed by Stephen T Lavalej into the next std update.
Here is a like to him talking about adding it. channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-/…
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.
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.
unfortunately, not supported by VC2010, even VC2012 I think, which both not support varidic tempalte parameter
X
Xeo

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.


It's not required. Hinted, but not required.
It's not only for convenience, it would also improve exception safety in certain cases. See Kerrek SB's answer for that.
K
Kerrek SB

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.


@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.
@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.
I think I need to re-watch shared_ptr and friends...
-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).
@Cheersandhth.-Alf: Yeah, that's true. I've since realised this. I'll edit the answer.
J
Johan Råde

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.


Nice to see this spelt out. Why not include the variadic template parameter, too?
@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.
The syntax is std::forward<Args>(args)..., which expands to forward<T1>(x1), forward<T2>(x2), ....
:I think forward actually always requires a template parameter, doesn't it?
@Howard: right! Forcing instantiation triggers the warning (ideone.com/GDNHb)
N
Nathan Binkert

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);