66

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?

Community
  • 1
  • 1
AlwaysLearning
  • 7,257
  • 4
  • 33
  • 68
  • @MeirGoldenberg, calling `reset` on a `shared_ptr` changes the managed object to that supplied by the pointer and it shouldn't be owner by any other `shared_ptr`. Whereas `operator=` would imply sharing ownership with another `shared_ptr` (unless of course you're doing move-assign). – Alejandro Jul 15 '15 at 19:06
  • "Shouldn't be owned"? Is this enforced or it's just an agreement? It would be nice if this comment along with an example became a full-fledged reply. – AlwaysLearning Jul 15 '15 at 19:10
  • 1
    This boils down to the difference between constructing a `shared_ptr` from a raw pointer and using `make_shared`, which should be trivial to find out. Hint: There is a difference and sometimes it can be significant. – Ulrich Eckhardt Jul 15 '15 at 19:15
  • @MeirGoldenberg, its enforced by undefined behavior (if you can consider that "enforced"). Consider what would happen if you `new`ed up some memory and called `reset` on two *different* `shared_ptr`s passing in that block of memory. When the two go out of scope, they'll try deleting the same block - hence UB. – Alejandro Jul 15 '15 at 19:17
  • 3
    @UlrichEckhardt I could have used a raw pointer in both cases: `p = std::shared_ptr(new int(5));` and `p.reset(new int(5));` as is. What is the difference now? – AlwaysLearning Jul 15 '15 at 19:19
  • Um, sorry, what I wrote might be confusing, as it only addresses a side-issue of your question. What I was referring was the question whether the "following two line [are] equivalent". One difference is the use of assignment vs `reset()`, the other is the use of raw `new` vs `make_shared`. – Ulrich Eckhardt Jul 15 '15 at 19:24
  • I am not really getting an answer to my question: what is the idiomatic use of `reset()`, where one would have a difficult time without it? – AlwaysLearning Jul 15 '15 at 19:44
  • @MeirGoldenberg I think you did not make your question clear, this comment really should be the question instead of if that 2 lines are equivalent. – Slava Jul 15 '15 at 19:58
  • @Slava Thank you. I have added an EDIT at the bottom and hope that the real meaning of the question is more explicit now. – AlwaysLearning Jul 15 '15 at 20:38
  • @AlwaysLearning `auto p = std::make_shared(1); p = nullptr;` I don't think it could be compiled. – John Mar 13 '22 at 08:42

4 Answers4

42

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.

Bondolin
  • 2,793
  • 7
  • 34
  • 62
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
6

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.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • I assume this won't apply when you create the original shared_ptr with make_shared? – Nir Friedman Jul 15 '15 at 20:04
  • 1
    @NirFriedman Less likely, but maybe not totally unreasonable. You could imagine a library implementation making a decision based on `sizeof(ControlBlock) + sizeof(T)` that it would be more worthwhile to avoid the dynamic allocation. It could then destroy the object, but not deallocate the control block and take ownership of the new pointer. – Praetorian Jul 15 '15 at 21:33
  • Generally, I think the use of `reset` encourages bad practice and should be avoided but I think this is a correct answer. There may some cases where use of `reset` is a performance optimization but I would check the improvement is measurable before making the change. – Chris Drew Jul 16 '15 at 06:22
  • 1
    @ChrisDrew Yes measuring would be important, because it's not obvious that reset is faster at all, in the larger scope, because make_shared creates both the control-block and object in a single allocation, meaning upon usage cache-locality could cause `make_shared` to win over `reset` depending on how the resulting `shared_ptr` is actually used. In other words it's not sufficient too only look at the performance of modifying a shared_ptr but rather the combined performance impact of modify + usage. (Which rules out small generalized tests without any real usage scenario). – AnorZaken Aug 30 '16 at 15:51
  • it's worth noting that both [libc++](https://github.com/llvm/llvm-project/blob/000cf84cf1bb2f5475600737c16c3ccf43096f58/libcxx/include/memory#L3294) and [libstdc++](https://github.com/gcc-mirror/gcc/blob/e0bdccac582c01c928a05f26edcd8f5ac24669eb/libstdc%2B%2B-v3/include/bits/shared_ptr_base.h#L1275) implement `reset` by constructing a new `shared_ptr` (along with a new control block) and swapping with `*this` – Dvir Yitzchaki Apr 07 '21 at 06:54
2

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.

Community
  • 1
  • 1
Alejandro
  • 3,040
  • 1
  • 21
  • 30
  • "The latter [i.e. assignment] implies shared ownership with another `shared_ptr` ". I think you mean to contrast it with `reset()`, which makes `p` an owner on a non-managed resource, which by definition means sole ownership. The more I read the more I am wondering: who would ever use `reset()`? Is there an example where this is the tool to use? – AlwaysLearning Jul 15 '15 at 19:39
  • @MeirGoldenberg, Updated with a potential example! – Alejandro Jul 15 '15 at 19:56
  • 2
    I'm not really sure why your example couldn't be done just as easily using the assignment operator. – Nir Friedman Jul 15 '15 at 19:58
  • 1
    `shared_resource = std::make_shared(...);` seems pretty concise to me. – Chris Drew Jul 16 '15 at 06:33
1

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.

Guvante
  • 18,775
  • 1
  • 33
  • 64
  • `shared_ptr` has only existed in the standard since C++11:http://en.cppreference.com/w/cpp/memory/shared_ptr. Boost does have a C++03 `shared_ptr` – NathanOliver Jul 15 '15 at 19:51
  • @NathanOliver: Got my lines crossed, rephrased to avoid that statement but still mention that it could have had an influence. (although the general ability to make a logical modification without copying/moving is something that is common in either case) – Guvante Jul 15 '15 at 20:26