14

A C++ std::shared_ptr<..> may be empty and it may also be null. Both of these concepts exist and they are not equivalent. Additionally, neither implication is always true between these cases.

The latter case is trivial to detect because operator bool provides precisely that test. According to the docs, it "checks if *this stores a non-null pointer, i.e. whether get() != nullptr."

Is there a test for the former case, the case where the thing is empty?

My use for this is quite simple. I have a class that has a static factory method. Inside the static factory method is a static local shared_ptr to an instance of the class, initialised to nullptr. The first call to this factory method constructs an instance of the class and initialises the static local shared_ptr before returning a copy of it - this is guarded by a mutex. That shared_ptr may be held by anything, copied and passed about, further copies may be acquired by additional calls to the static factory and, finally, when all the copies are destructed, the shared_ptr's deleter destructs the instance.

The instance itself is created and destructed with a legacy C API, wrapped by my class, and, although these instances are intended to be shared as singletons, they also need to be cleaned up when they are no longer needed - unlike singletons.

At the moment, I am using a null-check to decide whether the static local shared_ptr should be initialised or merely copied. I fear that this test will not work to detect the case where re-initialisation is required - for example, if something tries to acquire an instance at some time after all previous users gave up their references and the shared instance was deleted.

Or is it true that shared_ptr is reset to nullptr when the reference count falls to zero and the deleter is invoked? Also for custom deleters?

Also relevant: What is the difference between an empty and a null std::shared_ptr in C++?

4
  • 1
    How about checking if the counter is zero? Commented Feb 8, 2017 at 9:12
  • Because of the note: "However, this is not guaranteed in multithreaded environment."
    – Xharlie
    Commented Feb 8, 2017 at 9:14
  • 2
    When I read this, it sounds like there will always be a reference count of 1 for some shared_ptr object after the first initialization takes place. If not, for what object would you make this hypothetical call to determine its current state? It sounds like you might want weak_ptr behavior. Or maybe you could just clarify the question to make this point more clear (i.e. the case "if something tries to acquire an instance at some time after all previous users gave up their references and the shared instance was deleted").
    – cnettel
    Commented Feb 8, 2017 at 9:37
  • 1
    Your use case doesn't sound right. You have a static shared_ptr and you are handing out copies to it. This actually makes it singelton as the static instance is never cleaned up. If you have a singleton you may also use std::unique_ptr for the instance and return the raw pointer through your factory. Commented Feb 8, 2017 at 10:05

1 Answer 1

8

The static instance of shared_ptr will hold a reference, so the object will always have a ref count >= 1, and won't be deleted until static cleanup happens. As cnettel says in the comments, you need std::weak_ptr here.

weak_ptr is basically a shared_ptr which doesn't contribute to the ref count. It has an atomic cast to std::shared_ptr via the .lock() method. The resulting std::shared_ptr will convert to false if it is not initialized, so you know to reinitialize (or intialize for the first time).

Example:

std::shared_ptr<Obj> instance() {
    static std::weak_ptr<Obj> instance;
    static std::mutex lock;

    std::lock_guard<std::mutex> guard(lock);

    auto result = instance.lock();
    if (!result) {
        result = std::make_shared<Obj>();
        instance = result;
    }

    return result;
}
3
  • 1
    Alrighty. I guess I now have what I currently need. I still don't know the answer to the actual question I asked though. It seems like there is not a thread-safe solution to that problem... which is quite fine by me, since I no longer need one. Ta.
    – Xharlie
    Commented Feb 11, 2017 at 20:24
  • The problem with the question as stated is that a shared_ptr, once initialised, can never have a count <= 0, so the preconditions in the question will never occur. Checking if the counter is zero is just a check for if it was initialized, I think the warning is basically saying that if(ptr.use_count() == 0) ptr = new Obj(); is not thread safe, and you need a mutex. Commented Feb 11, 2017 at 21:25
  • also, checking for null (i.e. default operator bool) should always be your check for validity. You shouldn't (usually) care what the shared_ptr owns. It might own a containing object, or even own nothing (e.g. point into the stack) but if it ever dereferences to nullptr, you can't use it, so you should act like it is invalid. Technically, I guess you could have a pointer own something, but actually dereference to null, but that would have to be explicitly constructed very strangely, and I can't personally imagine a use for it. Commented Feb 11, 2017 at 21:33

Not the answer you're looking for? Browse other questions tagged or ask your own question.