Skip to content

Prefer not use reset

其实本质上是CppCoreGuidelines R.13: Perform at most one explicit resource allocation in a single expression statement

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

NOTE:

1、非常好的内容,它将本文的topic说得非常透彻;

2、应该从本质上来对问题进行分析:

本质原因是: 直接使用 new T() 所带来的一系列问题。

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

NOTE: reset是否会出现和new T()相同的问题?

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

A

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.

NOTE: 如何实现呢?

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.

A

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.

stackoverflow std::shared_ptr: reset() vs. assignment