0

To recap:

  • A shared_ptr contains a pointer to the managed object.
  • A shared_ptr contains a reference to a control block which is shared amongst shared_ptrs.
  • In case ptr to managed object = nullptr -> shared_ptr is considered null
  • In case ptr to control block = nullptr -> shared_ptr is considered empty

What's interesting is you can create null but non-empty shared_pts by casting nullptr to reference type like so (example borrowed from another thread.

#include <memory>
#include <iostream>

int main()
{
    std::cout << "std::shared_ptr<int> ptr1(static_cast<int*>(nullptr))" << std::endl;
    {
        std::shared_ptr<int> ptr1(static_cast<int*>(nullptr));
        std::cout << "\tuse count before copying ptr: " << ptr1.use_count() << std::endl;
        std::shared_ptr<int> ptr2 = ptr1;
        std::cout << "\tuse count  after copying ptr: " << ptr1.use_count() << std::endl;        
        std::cout << "\tptr1 is " << (ptr1 ? "not null" : "null") << std::endl;
    }
    std::cout << std::endl;
}

This will output the following:

std::shared_ptr<int> ptr1(static_cast<int*>(nullptr))
    use count before copying ptr: 1
    use count  after copying ptr: 2
    ptr1 is null

As you can see the shared_ptr fails the if()-statement and is considered null. But how is that possible when obviously there must be a non-null pointer to the task control block inside the shared_ptr object? I would have thought that only objects which equate to 0 memory (all fields set to 0) are considered null.

EDIT: I was actually confused because I didn't know about conversion operators. I just discovered them now and it totally makes sense that when conversion to bool is triggered we can just return the stored pointer.

glades
  • 3,778
  • 1
  • 12
  • 34
  • 4
    We can conclude that its conversion operator to `bool` checks only the object pointer and not the control block pointer. What exactly is your question? – HolyBlackCat Apr 14 '22 at 20:04
  • 4
    Yeah, I'm confused that OP is confused. This is the behavior that I'd expect when checking if a pointer is `nullptr` or not. – sweenish Apr 14 '22 at 20:10
  • 2
    The std::shared_ptr smart pointer is wrapping the pointer behavior of the data pointer, not of the control block machinery. – Eljay Apr 14 '22 at 20:13
  • Yeah, the control block is basically an implementation detail. The semantics being expressed are obviously meant to mirror regular pointers. –  Apr 14 '22 at 20:13
  • 1
    The ugliest, but also most true answer ever: because it's been designed this way. https://en.cppreference.com/w/cpp/memory/shared_ptr/operator_bool BTW, I'd also consider this rather intuitive... – alagner Apr 14 '22 at 20:17
  • @Frank that was going to be my question immediately: does the language definition even say that there is a "control block"? – Victor Eijkhout Apr 14 '22 at 20:23
  • 1
    @VictorEijkhout: Essentially. It says that there is a use count and when it gets incremented/decremented. It says that there is an object being managed which is associated with that use count. How that gets implemented is an implementation detail, but any implementation will require some additional memory to implement the standard requirements. – Nicol Bolas Apr 14 '22 at 21:11
  • @NicolBolas but it doesn't say anything about that additional memory being reachable through a `whatever*` pointer. – Victor Eijkhout Apr 14 '22 at 21:19
  • @VictorEijkhout: What does that matter? It does say what pointer must be destroyed (or rather, what pointer must be passed to the destruction functor provided) when the last owning pointer is destroyed. That requires that such things are "reachable"; just not through the reference returned by `*`. – Nicol Bolas Apr 14 '22 at 22:02
  • @sweenish This was more a question about the technicalities of the implementation. Because I somehow never heard about conversion operators I thought the object must be 0 with all fields to convert to bool 0. – glades Apr 15 '22 at 05:28
  • That’s well and good, but your original expectation left common sense behind. It happens. – sweenish Apr 15 '22 at 15:52

2 Answers2

1

I would have thought that only objects which equate to 0 memory (all fields set to 0) are considered null

There us no such thing as "null object" in C++.

A built-in pointer (including a pointer-to-member) can be a null pointer. Null pointers do not have to be represented with all zero bits. A character can be a null character. A number can be zero (not "null"). There are few other null things in C++, like null statements and null preprocessor directives, but those are not objects.

shared_ptr objects are not called "null pointers" by the C++ standard (they are not pointers, only built-in pointers are pointers). The standard only says things like r.get() == nullptr to express the idea of a shared_ptr not pointing to an object. Colloquially, in this case we may say that "the shared pointer is null". This usage is non-standard, and it has nothing to do with zero or non-zero bits inside a shared_ptr representation.

A shared_ptr is convertible to bool and this conversion enables things like r ? .. : ... and if (r) .... The conversion operator returns exactly get() == nullptr and not anything about zero or non-zero bits inside a shared_ptr representation.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
0

The use count is a property of a shared_ptr. But asking about that property is different from asking whether the pointer points to a live T. That question is purely about the pointer the shared_ptr instance holds, and that's the question explicit operator bool answers.

Indeed, the pointer being held by a shared_ptr<T> can be entirely independent of the object being managed. You can associate a managed state object with basically any pointer you like. Accessing the shared_ptr<T> will access the T it currently stores, but this does not have to be a pointer to the object being managed. So trying to combine the two questions just doesn't make sense.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982