Should I Use Shared_Ptr or Unique_Ptr

Differences between unique_ptr and shared_ptr

Both of these classes are smart pointers, which means that they automatically (in most cases) will deallocate the object that they point at when that object can no longer be referenced. The difference between the two is how many different pointers of each type can refer to a resource.

When using unique_ptr, there can be at most one unique_ptr pointing at any one resource. When that unique_ptr is destroyed, the resource is automatically reclaimed. Because there can only be one unique_ptr to any resource, any attempt to make a copy of a unique_ptr will cause a compile-time error. For example, this code is illegal:

unique_ptr<T> myPtr(new T);       // Okay
unique_ptr<T> myOtherPtr = myPtr; // Error: Can't copy unique_ptr

However, unique_ptr can be moved using the new move semantics:

unique_ptr<T> myPtr(new T);                  // Okay
unique_ptr<T> myOtherPtr = std::move(myPtr); // Okay, resource now stored in myOtherPtr

Similarly, you can do something like this:

unique_ptr<T> MyFunction() {
unique_ptr<T> myPtr(/* ... */);

/* ... */

return myPtr;
}

This idiom means "I'm returning a managed resource to you. If you don't explicitly capture the return value, then the resource will be cleaned up. If you do, then you now have exclusive ownership of that resource." In this way, you can think of unique_ptr as a safer, better replacement for auto_ptr.

shared_ptr, on the other hand, allows for multiple pointers to point at a given resource. When the very last shared_ptr to a resource is destroyed, the resource will be deallocated. For example, this code is perfectly legal:

shared_ptr<T> myPtr(new T);       // Okay
shared_ptr<T> myOtherPtr = myPtr; // Sure! Now have two pointers to the resource.

Internally, shared_ptr uses reference counting to track how many pointers refer to a resource, so you need to be careful not to introduce any reference cycles.

In short:

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

Hope this helps!

When should we use std::unique_ptr vs std::shared_ptr for class memeber variables?

A unique pointer should be used in cases where no other pointer variable will ask for access to the object / primitive data pointed to by the unique pointer. A shared pointer, however, exists for shared/concurrent access to a single resource such as a file.

I have a different understanding. The difference between unique and shared ptr is not the access. It is the lifecycle.

unique_ptr doesn't support copy semantics so it always has a single owner.

So I think for a class member variable, it depends on whether you want the class instance to be the only owner of the lifecycle.

Another benefit of unique_ptr is that, in most cases, it has the same size as a raw ptr. So it is smaller and faster than shared_ptr.

Should I use unique_ptr or shared_ptr in this case?

The main practical difference is what happens when the mainwindow is destroyed while a subwindow still exists and is using the network service:

  • If you use unique_ptr and pass raw pointers then you get undefined behavior.
  • If you use shared_ptr then the network service remains until all subwindows are destroyed.

Now, if this condition is impossible by design then the undefined behavior is not inherently a problem. If the condition happens anyway due to a bug then it might help you to detect the bug if the network service is destroyed along with the main window, which would happen if you use unique_ptr. Using unique_ptr expresses that the mainwindow is the only thing that owns the network service, the others merely use it as directed by the mainwindow.

On the other hand, if you later change the design and want to make the condition legal, or if you want to use the subwindows in a different way that means there's no single network service object that they all use and that outlives them all, then it will be easier if you used shared_ptr from the start. Using shared_ptr expresses that all the windows share ownership of the network service.

I don't think it's possible to say in general whether you should try to make your code continue working in the face of "impossible conditions". In this case it's very cheap to do so (shared_ptr is more expensive to copy than a raw pointer, of course, but cheap compared with creating a subwindow, and the code is structured the same either way). It can be useful to have the flexibility to make the "impossible condition" happen under certain circumstances, for example when unit testing the subwindow code. So probably use shared_ptr for flexibility. If enforcing the lifetime constraint is a big deal for other reasons then you might not want any flexibility, in which case avoid writing code that will only ever hide bugs.

Should I use shared_ptr or unique_ptr

I've been making some objects using the pimpl idiom, but I'm not sure whether to used shared_ptr or unique_ptr.

Definitely unique_ptr or scoped_ptr.

Pimpl is not a pattern, but an idiom, which deals with compile-time dependency and binary compatibility. It should not affect the semantics of the objects, especially with regard to its copying behavior.

You may use whatever kind of smart pointer you want under the hood, but those 2 guarantee that you won't accidentally share the implementation between two distinct objects, as they require a conscious decision about the implementation of the copy constructor and assignment operator.

However, these objects in a way really aren't being copied, as changes affect all copies, so I was wondering that perhaps using shared_ptr and allowing copies is some sort of anti-pattern or bad thing.

It is not an anti-pattern, in fact, it is a pattern: Aliasing. You already use it, in C++, with bare pointers and references. shared_ptr offer an extra measure of "safety" to avoid dead references, at the cost of extra complexity and new issues (beware of cycles which create memory leaks).


Unrelated to Pimpl

I understand unique_ptr is more efficient, but this isn't so much of an issue for me, as these objects are relatively heavyweight anyway so the cost of shared_ptr over unique_ptr is relatively minor.

If you can factor out some state, you may want to take a look at the Flyweight pattern.

Should I use shared_ptr or unique_ptr?

Short answer: depends.

It depends on if the pointer returned by getB may be stored/used somewhere while the owning A has gone out of scope. The difference is about ownership not about how many pointers you do have.

  • If the A still exists when you use the result of getB , you can store a unique_ptr and return a plain pointer (or a reference if getB can never return nullptr). That expresses "A owns B, and no-one else does".
  • If the A might go out of scope while you are using/holding the result of getB, but the B should go out of scope together with (or shortly after) the A, store a shared_ptr and return a weak_ptr.
  • If possibly many objects, including the callers of getB, may hold on to the B and there is no clear single owner, store and return shared_ptrs.

What is the advantage of a unique_ptr over a shared_ptr

a unique_ptr just seems like a special-case version of a shared_ptr where the reference count is restricted to one

This is not true, and is the crux of why you would use one or another. A shared_ptr is a reference counted pointer. In order for it to be thread safe it uses an atomic counter for the reference count. So that means for a shared_ptr you have the extra overhead of storing the reference counter, plus the execution overhead of checking/manipulating that counter in all the functions that affect it. This overhead can make a noticeable impact on performance

A unique_ptr, conversely, is a non reference counted pointer. It can get away without having a reference counter because it is not copyable. That means it is basically a zero cost abstraction for newing and deleteing a pointer.

So, if you never need to share ownership, or you can just move it from place to place, and you want self management, then you want a unique_ptr. This covers the vast majority of pointer use cases. When you truly need shared ownership then you can go ahead and use shared_ptr, but understand you suffer a performance penalty for that shared ownership.

Why shouldn't I use shared_ptr and unique_ptr always and instead use normal pointers?

As someone else mentioned, in C++ you have to consider ownership. That being said, the 3D networked multiplayer FPS I'm currently working on has an official rule called "No new or delete." It uses only shared and unique pointers for designating ownership, and raw pointers retrieved from them (using .get()) everywhere that we need to interact with C API's. The performance hit is not noticeable. I use this as an example to illustrate the negligible performance hit since games/simulations typically have the strictest performance requirements.

This has also significantly reduced the amount of time spent debugging and hunting down memory leaks. In theory, a well-designed application would never run into these problems. In real life working with deadlines, legacy systems, or existing game engines that were poorly designed, however, they are an inevitability on large projects like games... unless you use smart pointers. If you must dynamically allocate, don't have ample time for designing/rewriting the architecture or debugging problems related to resource management, and you want to get it off the ground as quickly as possible, smart pointers are the way to go and incur no noticeable performance cost even in large-scale games.

Should I always use shared_ptr to create a class instance(instead of just new)?

You shouldn't always use shared_ptr because there are more types of smart pointer than just shared_ptr. The Standard Library also provides unique_ptr for example. You must evaluate which better suits the task at hand.

Smart pointers in general are however the preferred way of safely handling dynamically allocated objects in modern C++. As a general rule of thumb, avoid the use of new and delete unless you encounter a problem that can't be solved without them.

As an aside, your example will not work as you expect.

auto s = std::shared_ptr(new S).get();

This will create a dangling pointer. The call to new dynamically allocates an object of type S. A pointer to it is given to the constructor for shared_ptr which now manages the object lifetime. You then assign a copy of the raw pointer of S to s via get(). At the end of the line the shared_ptr destructor frees S meaning that whatever s points to is undefined.

The correct way to create a shared pointer would be:

auto s = std::make_shared<S>();

When to use shared_ptr and when to use raw pointers?

Your analysis is quite correct, I think. In this situation, I also would return a bare B*, or even a [const] B& if the object is guaranteed to never be null.

Having had some time to peruse smart pointers, I arrived at some guidelines which tell me what to do in many cases:

  • If you return an object whose lifetime is to be managed by the caller, return std::unique_ptr. The caller can assign it to a std::shared_ptr if it wants.
  • Returning std::shared_ptr is actually quite rare, and when it makes sense, it is generally obvious: you indicate to the caller that it will prolong the lifetime of the pointed-to object beyond the lifetime of the object which was originally maintaining the resource. Returning shared pointers from factories is no exception: you must do this eg. when you use std::enable_shared_from_this.
  • You very rarely need std::weak_ptr, except when you want to make sense of the lock method. This has some uses, but they are rare. In your example, if the lifetime of the A object was not deterministic from the caller's point of view, this would have been something to consider.
  • If you return a reference to an existing object whose lifetime the caller cannot control, then return a bare pointer or a reference. By doing so, you tell the caller that an object exists and that she doesn't have to take care of its lifetime. You should return a reference if you don't make use of the nullptr value.


Related Topics



Leave a reply



Submit