[Solved] std::shared_ptr: reset() vs. assignment

This is a basic question, but I did not find a previous post about it. The title of the following question sounds like it might be the same question as mine, but the question itself does not match the title: is it better to use shared_ptr.reset or operator =?

I am confused about the purpose of the reset() member function of std::shared_ptr: what does it contribute in addition to the assignment operator?

To be concrete, given the definition:

auto p = std::make_shared<int>(1);
  1. Are the following two lines equivalent:

    p = std::make_shared<int>(5);
    p.reset(new int(5));
    
  2. What about these:

    p = nullptr;
    p.reset();
    

If the two lines are equivalent in both cases, then what is the purpose of reset()?


EDIT: Let me re-phrase the question to better emphasize its point. The question is: is there a case where reset() lets us achieve something that is not as easily achievable without it?

Solution #1:

When using reset() the parameter passed to reset need not be a managed object (nor can it be); whereas with = the right hand side must be a managed object.

So these two lines give you the same end result:

p = std::make_shared<int>(5); // assign to a newly created shared pointer
p.reset(new int(5)); // take control of a newly created pointer

But we cannot do:

p = new int(5); // compiler error no suitable overload
p.reset(std::make_shared<int>(5).get()); // uh oh undefined behavior

Without reset() you would not be able to reassign a shared pointer to a different raw pointer without creating a shared pointer and assigning it. Without = you wouldn’t be able to make a shared pointer point to another shared pointer.

Respondent: NathanOliver

Solution #2:

It’s possible for reset to avoid a dynamic memory allocation in certain cases. Consider the code

std::shared_ptr<int> p{new int{}};  // 1
p.reset(new int{});                 // 2

On line 1 there are 2 dynamic memory allocations happening, one for the int object and a second one for the shared_ptr‘s control block that’ll keep track of the number of strong/weak references to the managed object.

On line 2 there is again a dynamic memory allocation for a new int object. Within the body of reset the shared_ptr will determine that there are no other strong references to the previously managed int, so it must delete it. Since there aren’t any weak references either, it could also deallocate the control block, but in this case it would be prudent for the implementation to reuse the same control block because it would otherwise have to allocate a new one anyway.

The above behavior would not be possible if you always had to use assignment.

std::shared_ptr<int> p{new int{}};    // 1
p = std::shared_ptr<int>{new int{}};  // 2

In this case, the second call to the shared_ptr constructor on line 2 has already allocated a control block, so p will have to deallocate its own existing control block.

Respondent: Praetorian

Solution #3:

I won’t include the rationale behind your first sub-question of the difference between construction via make_shared or from a pointer, as this difference is highlighted in several different locations, including this excellent question.

However, I think it is constructive to distinguish between using reset and operator=. The former relinquishes ownership of the resource managed by the shared_ptr, either by destroying it if the shared_ptr happened to be the sole owner, or by decrementing the reference count. The latter implies shared ownership with another shared_ptr (unless you’re move constructing).

As I mentioned in the comments, it’s important that the pointer passed in to reset not be owned by another shared or unique pointer, because it would yield undefined behavior upon the destruction of the two independent managers – they both would attempt to delete the resource.

One use case of reset could be lazy initialization of a shared resource. You only want the shared_ptr to manage some resource, memory for example, if you really need it. Doing an outright allocation, such as:

std::shared_ptr<resource> shared_resource(new resource(/*construct a resource*/));

might be wasteful if its never actually needed. To do this with lazy initialization, something like this may apply:

std::shared_ptr<resource> shared_resource;
void use_resource()
{
       if(!shared_resource)
       {
            shared_resource.reset(new resource(...));
       }

       shared_resource->do_foo();
}

Using reset in this case is more concise than doing a swap or assigning to a temporary shared_ptr.

Respondent: Alejandro

Solution #4:

reset() changes the managed object of an existing shared_ptr.

p = std::shared_ptr(new int(5)); and p.reset(new int(5));

The former involves creating a new shared_ptr and moving it into a variable. The latter does not create a new object, it simply changes the underlying pointer in managed by the shared_ptr.

Put another way the two are meant to be used in different cases. Assignment is for when you have a shared_ptr and reset for when you have a raw pointer.

Another thing to keep in mind is that shared_ptr was available in boost before move assignment existed and influenced the latest version heavily. Without move assignment being able to change a shared_ptr without making a copy is beneficial as it saves you the bookkeeping of the extra object.

Respondent: Guvante

The answers/resolutions are collected from stackoverflow, are licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0 .

Leave a Reply

Your email address will not be published.