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