A smart pointer is like a regular (typed) pointer, like "char*", except when the pointer itself goes out of scope then what it points to is deleted as well. You can use it like you would a regular pointer, by using "->", but not if you need an actual pointer to the data. For that, you can use "&*ptr".
Why is auto_ptr deprecated? It takes ownership of the pointer in a way that no two pointers should contain the same object. Assignment transfers ownership and resets the rvalue auto pointer to a null pointer. Thus, they can't be used within STL containers due to the aforementioned inability to be copied.
A Smart Pointer is a wrapper class over a pointer with an operator like * and -> overloaded. The objects of the smart pointer class look like normal pointers. But, unlike Normal Pointers it can deallocate and free destroyed object memory.
A
unique_ptr
would not work because ofgetDevice()
, right?
No, not necessarily. What is important here is to determine the appropriate ownership policy for your Device
object, i.e. who is going to be the owner of the object pointed to by your (smart) pointer.
Is it going to be the instance of the Settings
object alone? Will the Device
object have to be destroyed automatically when the Settings
object gets destroyed, or should it outlive that object?
In the first case, std::unique_ptr
is what you need, since it makes Settings
the only (unique) owner of the pointed object, and the only object which is responsible for its destruction.
Under this assumption, getDevice()
should return a simple observing pointer (observing pointers are pointers which do not keep the pointed object alive). The simplest kind of observing pointer is a raw pointer:
#include <memory>
class Device {
};
class Settings {
std::unique_ptr<Device> device;
public:
Settings(std::unique_ptr<Device> d) {
device = std::move(d);
}
Device* getDevice() {
return device.get();
}
};
int main() {
std::unique_ptr<Device> device(new Device());
Settings settings(std::move(device));
// ...
Device *myDevice = settings.getDevice();
// do something with myDevice...
}
[NOTE 1: You may be wondering why I am using raw pointers here, when everybody keeps telling that raw pointers are bad, unsafe, and dangerous. Actually, that is a precious warning, but it is important to put it in the correct context: raw pointers are bad when used for performing manual memory management, i.e. allocating and deallocating objects through new
and delete
. When used purely as a means to achieve reference semantics and pass around non-owning, observing pointers, there is nothing intrinsically dangerous in raw pointers, except maybe for the fact that one should take care not to dereference a dangling pointer. - END NOTE 1]
[NOTE 2: As it emerged in the comments, in this particular case where the ownership is unique and the owned object is always guaranteed to be present (i.e. the internal data member device
is never going to be nullptr
), function getDevice()
could (and maybe should) return a reference rather than a pointer. While this is true, I decided to return a raw pointer here because I meant this to be a short answer that one could generalize to the case where device
could be nullptr
, and to show that raw pointers are OK as long as one does not use them for manual memory management. - END NOTE 2]
The situation is radically different, of course, if your Settings
object should not have the exclusive ownership of the device. This could be the case, for instance, if the destruction of the Settings
object should not imply the destruction of the pointed Device
object as well.
This is something that only you as a designer of your program can tell; from the example you provide, it is hard for me to tell whether this is the case or not.
To help you figure it out, you may ask yourself whether there are any other objects apart from Settings
that are entitled to keep the Device
object alive as long as they hold a pointer to it, instead of being just passive observers. If that is indeed the case, then you need a shared ownership policy, which is what std::shared_ptr
offers:
#include <memory>
class Device {
};
class Settings {
std::shared_ptr<Device> device;
public:
Settings(std::shared_ptr<Device> const& d) {
device = d;
}
std::shared_ptr<Device> getDevice() {
return device;
}
};
int main() {
std::shared_ptr<Device> device = std::make_shared<Device>();
Settings settings(device);
// ...
std::shared_ptr<Device> myDevice = settings.getDevice();
// do something with myDevice...
}
Notice, that weak_ptr
is an observing pointer, not an owning pointer - in other words, it does not keep the pointed object alive if all other owning pointers to the pointed object go out of scope.
The advantage of weak_ptr
over a regular raw pointer is that you can safely tell whether weak_ptr
is dangling or not (i.e. whether it is pointing to a valid object, or if the object originally pointed to has been destroyed). This can be done by calling the expired()
member function on the weak_ptr
object.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With