42

I'm trying to wrap my head around the new idioms for C++11.

It seems that with shared_ptr at least, there is a substantive difference between using new T() and make_shared<T>().

But what of resetting a shared pointer to point to a new instance of something. Previously, I would typically use reset(new T()) member. However, doesn't this suffer from the same problem as not using make_shared() in the first place? (i.e. it doesn't allow make_shared to allocate the object, therefore it is forced to place the ref count in a separate allocation instead of in the same allocation as the T itself?)

Is it simply better going forward to use:

   mysharedptr = make_shared<T>(args...);

Or is there a better way?

And shouldn't reset offer variadic forwarding of arguments as make_shared does, so that one could write mysharedptr.reset(args...);?

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
Mordachai
  • 9,412
  • 6
  • 60
  • 112

2 Answers2

43

There is indeed a substantial difference between:

shared_ptr<T> sp(new T());

And:

shared_ptr<T> sp = make_shared<T>();

The first version performs an allocation for the T object, then performs a separate allocation to create the reference counter. The second version performs one single allocation for both the object and the reference counter, placing them in a contiguous region of memory, resulting in less memory overhead.

Also, some implementations are able to perform further space optimizations in the case of make_shared<> (see the "We Know Where You Live" optimization done by MS's implementation).

However, that is not the only reason why make_shared<> exists. The version based on explicit new T() is not exception-safe in some situations, especially when invoking a function that accepts a shared_ptr.

void f(shared_ptr<T> sp1, shared_ptr<T> sp2);

...

f(shared_ptr<T>(new T()), shared_ptr<T>(new T()))

Here, the compiler could evaluate the first new T() expression, then evaluate the second new T() expression, then construct the corresponding shared_ptr<> objects. But what if the second allocation causes an exception before the first allocated object is bound to its shared_ptr<>? It will be leaked. With make_shared<>(), this is not possible:

f(make_shared<T>(), make_shared<T>())

Because allocated objects are bound to the respective shared_ptr<> objects inside each function call to make_shared<>(), this call is exception-safe. This is yet another reason why naked new should never be used unless you really know what you are doing. (*)

Considering your remark about reset(), you are right in observing that reset(new T()) will perform separate allocations for the counter and the object, just as the construction of a new shared_ptr<> will perform a separate allocation when a raw pointer is passed as an argument. Therefore, an assignment using make_shared<> is preferable (or even a statement such as reset(make_shared<T>())).

Whether or not reset() should support a variadic argument list, this is probably more of a kind of open discussion for which StackOverflow is not a good fit.

(*) There are a few situations that still require it. For instance, the fact that the C++ Standard Library lacks a corresponding make_unique<> function for unique_ptr, so you'll have to write one yourself. Another situation is when you do not want the object and the counter to be allocated on a single memory block, because the presence of weak pointers to the object will prevent the entire block from being deallocated, even though no more owning pointers to the object exist.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • I think it's good to note that the exception-safety argument is enough (for me at least) to say we should no longer even use `new` at all, but `make_unique` at a bare minimum. This prevents any possible leaks by design for any allocation. – GManNickG Feb 12 '13 at 16:40
  • @GManNickG: Yes, `make_unique` is the exception I had in mind for allowing the use of `new`. Btw, do you happen to know why there's no `make_unique` in the standard library? – Andy Prowl Feb 12 '13 at 16:43
  • 2
    Simple oversight. Btw, you still might want to *not* use `make_shared` if you're memory-pressed, since the optimization will keep the memory-block itself alive until there are no more weak pointers. – Xeo Feb 12 '13 at 16:43
  • I wonder though about make_unique vs. unique_ptr::reset? Isn't there an optimization available in reusing the allocated T? Couldn't reset (assuming it was variadic and used perfect forwarding) simply reuse the existing memory slot of T? – Mordachai Feb 12 '13 at 17:05
  • @Mordachai, yes, assuming `reset` worked more like a container's `emplace` function it could reuse memory, but that assumption doesn't hold and is a fairly big design change. The C++11 smart pointers are only responsible for taking ownership of an object, not its creation or allocating memory for it. Rather than destroying, deallocating, allocating and constructing, maybe you just want to do `*up = T(args...)` instead? – Jonathan Wakely Feb 12 '13 at 17:20
  • Side discussion: I'd go with `shared_ptr::emplace(Args&&... args)` rather than overloading `reset`. – Yakk - Adam Nevraumont Feb 12 '13 at 20:52
  • There are also cases where the `make_shared<>()` is not possible such as when the constructor and/or destructor are private (i.e. you created some form of singleton or at least fully controlled allocation/deallocation of your class objects.) – Alexis Wilke Apr 24 '16 at 23:21
6

Correct, reset(new T...) suffers all the issues of shared_ptr(new T...); it will result in double allocation and also is non-exception-safe (there's not much chance of a leak, unless bad_alloc happens within reset).

reset is documented as equivalent to shared_ptr<T>(ptr).swap(*this), so you could also write:

make_shared<T>(args...).swap(mysharedptr);

Assignment from make_shared<T> is almost equivalent, the only difference being the relative order of the deletion of the old T and the destruction of the temporary shared_ptr, which is not observable.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • I'm not sure what is best. But I think for now I am going with your swap for std::unique_ptr and assignment for std::shared_ptr, just because this avoids having to write `myptr = std::move(make_unique(...))` – Mordachai Feb 12 '13 at 17:12
  • 4
    @Mordachai the `move` isn't necessary; the return value of `make_shared` or `make_unique` is a prvalue, so binds to a rvalue reference. – ecatmur Feb 12 '13 at 17:18
  • Thanks. I must've made some other mistake and misunderstood it. I see now that simple assignment works fine. – Mordachai Feb 12 '13 at 17:48
  • and is required for polymorphic uses `p = make_unique()` – Mordachai Feb 12 '13 at 17:58