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 it
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.
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
.
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.
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.