Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

is it better to use shared_ptr.reset or operator =?

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

like image 275
Mordachai Avatar asked Feb 12 '13 16:02

Mordachai


People also ask

When should you use shared_ptr?

So, we should use shared_ptr when we want to assign one raw pointer to multiple owners. // referring to the same managed object. When to use shared_ptr? Use shared_ptr if you want to share ownership of a resource.

Why would you choose shared_ptr instead of unique_ptr?

In short: Use unique_ptr when you want a single pointer to an object that will be reclaimed when that single pointer is destroyed. Use shared_ptr when you want multiple pointers to the same resource.

Is shared_ptr slow?

shared_ptr are noticeably slower than raw pointers. That's why they should only be used if you actually need shared ownership semantics. Otherwise, there are several other smart pointer types available. scoped_ptr and auto_ptr (C++03) or unique_ptr (C++0x) both have their uses.

What is the best way to delete an object managed by shared_ptr?

If shared_ptr::unique returns true, then calling shared_ptr::reset will delete the managed object. However, if shared_ptr::unique returns false, it means there are more than one shared_ptr s sharing ownership of that object.


2 Answers

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.

like image 77
Andy Prowl Avatar answered Oct 04 '22 19:10

Andy Prowl


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.

like image 44
ecatmur Avatar answered Oct 04 '22 18:10

ecatmur