185

I'm having trouble understanding the usage of smart pointers as class members in C++11. I have read a lot about smart pointers and I think I do understand how unique_ptr and shared_ptr/weak_ptr work in general. What I don't understand is the real usage. It seems like everybody recommends using unique_ptr as the way to go almost all the time. But how would I implement something like this:

class Device {
};

class Settings {
    Device *device;
public:
    Settings(Device *device) {
        this->device = device;
    }

    Device *getDevice() {
        return device;
    }
};    

int main() {
    Device *device = new Device();
    Settings settings(device);
    // ...
    Device *myDevice = settings.getDevice();
    // do something with myDevice...
}

Let's say I would like to replace the pointers with smart pointers. A unique_ptr would not work because of getDevice(), right? So that's the time when I use shared_ptr and weak_ptr? No way of using unique_ptr? Seems to me like for most cases shared_ptr makes more sense unless I'm using a pointer in a really small scope?

class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(std::shared_ptr<Device> device) {
        this->device = device;
    }

    std::weak_ptr<Device> getDevice() {
        return device;
    }
};

int main() {
    std::shared_ptr<Device> device(new Device());
    Settings settings(device);
    // ...
    std::weak_ptr<Device> myDevice = settings.getDevice();
    // do something with myDevice...
}

Is that the way to go? Thanks very much!

michaelk
  • 2,107
  • 3
  • 15
  • 18
  • 7
    It helps to be really clear as to lifetime, ownership and possible nulls. For example, having passed `device` to the constructor of `settings`, do you want to still be able to refer to it in the calling scope, or only via `settings`? If the latter, `unique_ptr` is useful. Also, do you have a scenario where the return value of `getDevice()` is `null`. If not, just return a reference. – Keith Mar 26 '13 at 22:53
  • 2
    Yes, a `shared_ptr` is correct in 8/10 cases. The other 2/10 are split between `unique_ptr` and `weak_ptr`. Also, `weak_ptr` is generally used to break circular references; I'm not sure that your usage would be considered correct. – Collin Dauphinee Mar 26 '13 at 22:53
  • 2
    First of all, what ownership do you want for the `device` data member? You first have to decide that. – juanchopanza Mar 26 '13 at 22:56
  • 2
    Ok, I understand that as the caller I could use a `unique_ptr` instead and give the ownership up when calling the constructor, if I know I won't need it anymore for now. But as the designer of the `Settings` class I don't know if the caller wants to keep a reference as well. Maybe the device will be used in many places. Ok, maybe that's exactly your point. In that case, I would not be the sole owner and that's when I would use shared_ptr, I guess. And: so smart points do replace pointers, but not references, right? – michaelk Mar 26 '13 at 23:02
  • this->device = device; Also use initialization lists. – Nils Mar 27 '13 at 11:02
  • As I posted on the accepted answer, there are many implied limitations you accept by making a `unique_ptr` a member variable, probably chief among them is the implicit deletion of the standard copy constructor. This deletion possibly makes the use of your class highly unintuitive for any programmer downstream. See: https://stackoverflow.com/questions/16030081/copy-constructor-for-a-class-with-unique-ptr – ldog Apr 18 '20 at 20:36

2 Answers2

231

A unique_ptr would not work because of getDevice(), 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.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • Thx a lot, thinking about the time of destruction was exactly what helped me to understand the different in usage. Also the note considering the raw pointer was very helpful. If I understand it correctly a `weak_ptr` would also be possible here (as both are observing pointers), but not necessary? – michaelk Mar 26 '13 at 23:21
  • 4
    @LKK: Yes, correct. A `weak_ptr` is always an alternative to raw observing pointers. It is safer in a sense, because you could check if it is dangling before dereferencing it, but it also comes with some overhead. If you can easily guarantee that you are not going to dereference a dangling pointer, then you should be fine with observing raw pointers – Andy Prowl Mar 26 '13 at 23:24
  • 1
    @LKK, probably the easiest way to make sure that you won't dereference a dangling pointer is not to store it, or pass it to someone who will. Of course, you should also make sure that the owner isn't getting destroyed during the current scope, but this is easier to see since it's local. It's trickier in a multithreaded environment though. – enobayram Mar 27 '13 at 06:47
  • 9
    In the first case it would probably even be better to let `getDevice()` return a reference, whouldn't it? So the caller would not have to check for `nullptr`. – vobject Mar 27 '13 at 10:23
  • @AndyProwl Regarding the first case, just make getDevice() return by reference. AFAIU, the Settings guarantees there must be a device attached, it's not optional. So, no use of pointers needed. – mloskot Mar 27 '13 at 10:24
  • @vobject: In this case it could return a reference, yes, or a reference wrapper. However, this can't be generalized to the case where a returned pointer *could* be null, so I thought I'd just put a pointer in there (my main goal in this answer was to explain the meaning of observing pointers vs owning pointers in a generalized context, and to point out that raw pointers are not that bad when *observing* pointers are needed). – Andy Prowl Mar 27 '13 at 10:35
  • 2
    @vobject: Also, returning a reference would make it possible for the user to erroneously write something like: `Device myDevice = settings.getDevice();` when they actually meant `Device& myDevice = settings.getDevice();` (forgetting the `&`), which won't happen when returning a pointer. I also feel like checking against `nullptr` is not needed here, because the function is always guaranteed to return a valid pointer. – Andy Prowl Mar 27 '13 at 10:36
  • @AndyProwl and employing usage of `auto myDevice = settings.getDevice()` has potential of avoiding the mistake of forgetting the `&`. – oblitum Mar 27 '13 at 16:19
  • 6
    @chico: Not sure what you mean. `auto myDevice = settings.getDevice()` will create a new instance of type `Device` called `myDevice` and copy-construct it from the one referenced by the reference that `getDevice()` returns. If you want `myDevice` to be a reference, you need to do `auto& myDevice = settings.getDevice()`. So unless I am missing something, we're back in the same situation we had without using `auto`. – Andy Prowl Mar 27 '13 at 16:28
  • @AndyProwl true, need to revise auto (and all the universal references stuff). I've expected auto to resolve to a reference type for the returning reference, my mistake. – oblitum Mar 28 '13 at 00:59
  • @AndyProwl It seems likely in this example that `Device` should be non-copyable anyway (private copy constructor), which would avoid the issue with users accidentally writing `Devide myDevice = settings.getDevice()`. – Edward Loper Mar 28 '13 at 17:17
  • 2
    Returning an observing raw pointer implies multiple responsibilities. The pointer semantics imply `nullptr` is a possible, and valid, result. It would only be through convention or explicit documentation that `nullptr` needn't be checked. I think a (`const`) reference should be the the choice by default, with an observing pointer as an alternative if you wish to also express that the type is optional. – Bret Kuhns Mar 28 '13 at 23:23
  • 1
    Keep an eye on `std::optional` for possibly C++14, or check out `boost::optional` in order to fully express the right semantics. Also, a web search for `exempt_ptr` should return an ISO C++ proposal for a "dumb smart pointer" who's sole purpose is to express "observing pointer" semantics. – Bret Kuhns Mar 28 '13 at 23:25
  • 1
    @BretKuhns: As I mentioned in a previous comment, it is true that in this case we could return a reference, but my goal in the answer was just to provide a simple solution that 1) could be generalized (applies also when the allowed pointer could be `null`), and 2) showed that raw pointers are bad only when used for manual memory management. But I agree that in this particular case we could/should return a reference. – Andy Prowl Mar 28 '13 at 23:59
  • @AndyProwl I Saw the earlier comments, but wanted to emphasize that returning the observing pointer in this case is misleading and doesn't convey the correct information to the caller. Earlier comments were more literal, whereas I'm speaking to the API and maintainability of the code. – Bret Kuhns Mar 29 '13 at 11:57
  • I have a question: why not return a `std::unique_ptr&` instead of a `T*`? – user703016 Jan 29 '14 at 21:50
  • 3
    @Purrformance: Because you don't want to give away the ownership of the object - handing a modifiable `unique_ptr` to a client opens the possibility that the client will move from it, thus acquiring ownership and leaving you with a null (unique) pointer. – Andy Prowl Jan 29 '14 at 21:52
  • @Any Thank you, that makes sense. What about a `std::unique_ptr const&`? – user703016 Jan 29 '14 at 21:54
  • 8
    @Purrformance: While that would prevent a client from moving (unless the client is a mad scientist keen on `const_cast`s), I personally wouldn't do it. It exposes an implementation detail, i.e. the fact that ownership is unique and realized through a `unique_ptr`. I see things this way: if you want/need to pass/return ownership, pass/return a smart pointer (`unique_ptr` or `shared_ptr`, depending on the kind of ownership). If you don't want/need to pass/return ownership, use a (properly `const`-qualified) pointer or reference, mostly depending on whether the argument can be null or not. – Andy Prowl Jan 29 '14 at 22:49
  • @AndyProwl If `Settings` has no interest in `Device` life (just uses it), is it correct to have a raw pointer as class field or is it anyway better a smart pointer? – Marco Stramezzi Oct 18 '17 at 10:35
  • @MarcoStramezzi Yes, in that case a raw pointer would be correct. – Andy Prowl Oct 18 '17 at 14:59
  • @AndyProwl: You recommend using a raw pointer/reference as getDevice() return type. Lets say Settings owns the Device. Also lets say I make a mistake by using getDevice() the wrong way, storing the returned raw pointer/reference longer than the Settings object lives. I would get a hard to debug undefined behavior. Could I use smart pointers to prevent that or at least make it easier to debug? I imagine using shared_ptr as member and as return value. I would need to throw an exception on destruction if the shared_ptr member does not hold the last reference. Is this feasible? – Silicomancer Mar 06 '20 at 23:14
  • This answer is highly incomplete. For example, it does not mention all the implicit limitations you accept by choosing a `unique_ptr` over a `shared_ptr`, probably chief among them is the implicit deletion of the standard copy constructor, see [1]. Please thoroughly research the topic before posting an incomplete answer. [1] https://stackoverflow.com/questions/16030081/copy-constructor-for-a-class-with-unique-ptr – ldog Apr 18 '20 at 20:32
2
class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(const std::shared_ptr<Device>& device) : device(device) {

    }

    const std::shared_ptr<Device>& getDevice() {
        return device;
    }
};

int main()
{
    std::shared_ptr<Device> device(new Device());
    Settings settings(device);
    // ...
    std::shared_ptr<Device> myDevice(settings.getDevice());
    // do something with myDevice...
    return 0;
}

week_ptr is used only for reference loops. The dependency graph must be acyclicdirected graph. In shared pointers there are 2 reference counts: 1 for shared_ptrs, and 1 for all pointers (shared_ptr and weak_ptr). When all shared_ptrs are removed, the pointer is deleted. When pointer is needed from weak_ptr, lock should be used to get the pointer, if it exists.

Naszta
  • 7,560
  • 2
  • 33
  • 49
  • So if I understand your answer correctly, smart pointers do replace raw pointers, but not necessarily references? – michaelk Mar 26 '13 at 23:10
  • Are there actually *two* reference counts in a `shared_ptr`? Can you please explain why? As far as I understand, `weak_ptr` doesn't have to be counted because it simply creates a new `shared_ptr` when operating on the object (if the underlying object still exists). – Björn Pollex Mar 27 '13 at 08:35
  • @BjörnPollex: I created a short example for you: [link](http://store.naszta.hu/main/shrptr.h). I haven't implemented everything just the copy constructors and `lock`. [boost](http://boost.org) version is also thread safe on reference counting (`delete` is called only once). – Naszta Mar 27 '13 at 20:45
  • @Naszta: Your example shows that it is *possible* to implement this using two reference counts, but your answer suggests that this is *required*, which I don't believe it is. Could you please clarify this in your answer? – Björn Pollex Mar 28 '13 at 08:01
  • @BjörnPollex: Look, this is the way how boost implementation does work. In boost atomic counters are used for thread safe. That's all. If you don't beleave me, you could easily check it in boost implementation (or in VS2012 its own). – Naszta Mar 28 '13 at 08:11
  • 1
    @BjörnPollex, in order for `weak_ptr::lock()` to tell if the object has expired it must inspect the "control block" that contains the first reference count and pointer to the object, so the control block must not be destroyed while there are any `weak_ptr` objects still in use, so the number of `weak_ptr` objects must be tracked, which is what the second reference count does. The object gets destroyed when the first ref count drops to zero, the control block gets destroyed when the second ref count drops to zero. – Jonathan Wakely Mar 31 '13 at 19:54