Search code examples
c++c++17shared-ptrlanguage-design

Why does the standard allow empty but non-null shared_ptr?


  1. This question is different from std::shared_ptr which is empty but not null. That question is about solving a particular problem, while I'm asking about the rationale and design reasons behind a certain standard feature.
  2. This question is a predecessor to another question: Why is empty weak_ptr required to store null pointer, while empty shared_ptr is allowed to store non-null pointer? They share the same context, to some extent. However, I believe this question is self-contained and answerable in itself.

The C++ standard offers an aliasing constructor for shared_ptr:

template <typename T>
class shared_ptr
{
    template <typename U>
    shared_ptr(const shared_ptr<U>& r, element_type* ptr);
};

Such constructor means that a shared_ptr object can be created in a way that it owns one object, same as owned by r, while at the same time storing a pointer to another object -- ptr.

Also, the standard explicitly allows using this constructor to create a shared_ptr which doesn't own anything (thus meeting the definition of 'empty'), but still points to some object (thus qualified as 'non-null'):

[util.smartptr.shared.const]:
17. [Note 2: This constructor allows creation of an empty shared_­ptr instance with a non-null stored pointer. — end note]

Construction of an empty but non-null shared_ptr could have been easily forbidden, for example by demanding that attempted construction of an empty shared_ptr instance with a non-null stored pointer should throw an exception.

But the standard chose to allow it. I wonder why and what for.

What is the intended and canonical use scenario for an empty but non-null shared_ptr?


Solution

  • The aliasing constructor was added to std::shared_ptr during standardization, in N2351 "Improving shared_ptr for C++0x, Revision 2":

    This feature extends the interface of shared_ptr in a backward-compatible way that increases its expressive power and is therefore strongly recommended to be added to the C++0x standard. It introduces no source- and binary compatibility issues.

    At this time it was already noted that:

    [Note: This constructor allows creation of an empty shared_ptr instance with a non-NULL stored pointer. --end note.]

    The aliasing constructor was at the same time added to boost::shared_ptr, which acted as a testbed: https://github.com/boostorg/smart_ptr/commit/54e12d03fdfec63b4d8ff41991c4e64af6b1b4b4 https://github.com/boostorg/smart_ptr/commit/ce72827dc73ac652ed07002b75f32e0171119c09

    As for why this is permitted: there is a clear alternative, which is for the user to construct a shared_ptr from an existing pointer and a no-op deleter:

    auto sp = shared_ptr(p, [](auto){})
    

    However, this is less efficient than aliasing the empty state (since a separate control block must be allocated) and less expressive, since it is not possible to determine that the deleter is a no-op. So there is little reason to forbid the former.