Question

in the mainwindow of my QT app, I use a std::shared_ptr to hold a pointer to an instance of my network service which manages all the connections to multiple clients. Now, I have to pass this pointer to multiple sub windows so that they can communicate with the clients.

Do i better use a std::shared_ptr member variable in the main- and sub-windows and pass copy it when creating the subwindows, or is it better to use a std::unique_ptr and pass a raw pointer to the sub Windows, as the mainwindow will outlive the subwindows anyway?

Thanks a lot!

Was it helpful?

Solution

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.

OTHER TIPS

Actually, there is a 3rd alternative you have not explored.

  • Passing shared_ptr allows you to have multiple owners, however it seems in your case this is unnecessary
  • Using unique_ptr and passing a raw pointer enforces a single owner, but in case of bug then you are faced with undefined behavior (a crash).

The 3rd alternative is to combine both approaches using weak_ptr:

  • The main window owns the device in a shared_ptr
  • The sub windows are handed down a weak_ptr

When the sub window needs to communicate using the device, they will use lock() on the weak_ptr to temporarily obtain a shared_ptr. You can then assert or throw if the shared_ptr is empty (it's a bug), and otherwise use it to communicate with the device, and then let it go as you are done.


EDIT: as Steve Jessop noted another assertion is useful (and achievable): ensuring that when the main window destroys the shared_ptr it was the last owner and the device is released (to protect against an accidental leak of ownership).

The naive way, asserting that it's unique before destruction, unfortunately suffers from a race-condition; between the call to unique and the actual destruction, a call to a weak_ptr::lock could create a new owner.

It can be done simply, however:

  1. Create a new weak_ptr called monitor from your shared_ptr.
  2. Reset the shared_ptr.
  3. Call monitor.lock(), if it returns a non-null shared_ptr then there is an owner in the wild.

The question about using std::shared_ptr or std::unique_ptr is mostly one of ownership. Will there be only one single owner at a time to the object? Or will there be multiple owners to the object?

In your case it seems like you want multiple owners, so then std::shared_ptr is the way to go.

Don't use std::unique_ptr and then pass around the raw wrapped pointer. It will come back to bite you when you forget it and the std::unique_ptr object goes out of scope while someone else still have access to the raw pointer.

It sounds to me like the network service is an object which should exist for the life of your program. In which case, it's not certain that any type of smart pointer should be used; the most obvious solution would be a local variable in main. At any rate, the first thing to ask yourself is what the lifetime of the object should be. If that lifetime corresponds to the scope of main (or the scope of any other function), the local variable is the way to go. If that lifetime should correspond to that of the main window, a member variable of the main window would be the appropriate solution. It all depends on how your application specifies the object lifetimes (which is a design issue, which can't be solved by low level programming techniques).

About the only case you might need to use pointers is if the type is polymorphic, and the actual type is not known until runtime (e.g. because it is determined by entries in a configuration file). In this case, you'll have to manage the lifetime yourself, but if it does correspond to a scope, std::unique_ptr might be a good solution, since if not moved, it precisely emulates the lifetime of a scoped variable. (I'd be very sceptical of std::shared_ptr here; the lifetime should probably be deterministic, and not dependent on whether someone happens to hold a pointer to it. I can also imagine scenarios where the network server would hold pointers to its clients, with a risk of cycles.)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top