2
votes

http://www.cplusplus.com/reference/memory/shared_ptr/ says

A shared_ptr that does not own any pointer is called an empty shared_ptr. A shared_ptr that points to no object is called a null shared_ptr and shall not be dereferenced. Notice though that an empty shared_ptr is not necessarily a null shared_ptr, and a null shared_ptr is not necessarily an empty shared_ptr.

Am I able to create an empty std::shared_ptr, i.e. one which does not own anything but which is not null, i.e. contains payload?

The usecase is to marry "legacy" code with the modern pointers: Given that foo's call syntax is not to be changed,

void foo(const MyClass* p) {
   if (p == nullptr) {
       auto defaultObject = std::make_shared<MyClass>("some", "parameters");
       defaultObject->doSomething();
       ...
       defaultObject->doSomethingElse();
       // The default object must be destroyed again
   } else {
       p->doSomething();
       ...
       p->doSomethingElse();
       // p must not be destroyed, its memory is managed somewhere else
   }
}

is there an implementation for doNotOwnButStillPointTo() which allows this:

void foo(const MyClass* p) {
    std::shared_ptr<MyClass> wrapper;
    if (p == nullptr) {
        // Create a default object
        wrapper = std::make_shared<MyClass>("some", "parameters");
    } else {
        wrapper = doNotOwnButStillPointTo(p);
    }

    wrapper->doSomething();
    ...
    wrapper->doSomethingElse();
    // p mus not be destroyed, the default object (if created) shall be
}

Or, to not fall for the XY-Problem, is there a different smart pointer available or none at all?

  • However, I want to add, that the line std::make_shared<MyClass>("some", "parameters") is actually a call to a more complex function which creates a shared_ptr. I'd like to retain this function and use its result
4
cplusplus.com is a poor quality website for C++. cppreference.com is much better. std::shared_ptr can be empty shared_ptr may have a non-null stored pointing if an aliasing constructor was used to create it. Such as from a downcast.Eljay
@PhilLab: '"The usecase is to marry "legacy" code with the modern pointers" Why not just use unique_ptr here? In this case, your shared_ptr never leaves your scope, so there's no need for its ownership semantics to be shared.Nicol Bolas

4 Answers

3
votes

The shared_ptr has a deleter. This is a polymorphic procedure of destroying the underlying object. You may have an empty deleter:

void foo(const MyClass* p) {
    std::shared_ptr<MyClass> wrapper;
    if (p == nullptr) {
        // Create a default object
        wrapper = std::make_shared<MyClass>("some", "parameters");
    } else {
        wrapper = std::shared_ptr<MyClass>(p, [](MyClass*){});
    }

    wrapper->doSomething();
    ...
    wrapper->doSomethingElse();
    // p mus not be destroyed, the default object (if created) shall be
}

This however leads to a bad design. Returning back to the ProblemXY: what is the purpose? Someone may pass you an object as a raw pointer (but may pass a nullptr). You wish to create a local in case the nullptr is provided instead of a real object. And you probably wish to prevent memory leak. Ok.

void foo(const MyClass* p) {
    std::shared_ptr<MyClass> local;
    if (p == nullptr) {
        // Create a default object
        local = std::make_shared<MyClass>("some", "parameters");
        p = local.get();
    }

    // p is always valid, local will always be destroyed (if exists)
    p->doSomething();
    ...
    p->doSomethingElse();
}
3
votes

What you are looking for is shared_ptr's custom deleter constrcutor. This was built to allow a shared pointer to take over a pointer from somewhere else and when it is destroyed, it will run your custom code instead of calling delete. In this case, you custom code will just do nothing since you don't want to delete the pointer. That would look like

void foo(const MyClass* p) {
    std::shared_ptr<MyClass> wrapper;
    if (p == nullptr) {
        // Create a default object
        wrapper = std::make_shared<MyClass>("some", "parameters");
    } else {
        wrapper = std::shared_ptr(p, [](auto){ });
    }

    wrapper->doSomething();
    ...
    wrapper->doSomethingElse();
    // p mus not be destroyed, the default object (if created) shall be
}
2
votes

Am I able to create an empty std::shared_ptr, i.e. one which does not own anything but which is not null, i.e. contains payload?

It is indeed possible. The linked page is correct in saying empty shared_ptr is not necessarily a null shared_ptr.

Such shared pointer can be created using the constructor

template< class Y > 
shared_ptr(const shared_ptr<Y>&, element_type*);

If you pass an empty null shared pointer as first parameter, and non-null pointer as second, you then get an empty non-null shared pointer.

The constructor is usually used to point to a member or alias of an owned object but it happens to also match this corner case. Take special care to not let this shared pointer leak to the outside of the scope of this function.

In your case:

wrapper = {std::shared_ptr<void>{}, p};

Or, to not fall for the XY-Problem, is there a different smart pointer available or none at all?

Another approach is to use the shared pointer only for ownership of the default object, and call the functions through p instead:

std::shared_ptr<MyClass> wrapper;
if (p == nullptr) {
    // Create a default object
    wrapper = std::make_shared<MyClass>("some", "parameters");
    p = wrapper.get();
}

p->doSomething();
...
p->doSomethingElse();
1
votes

The site cplusplus.com tends to not be as strict about its use of language, particularly relative to the standard. It prefers more vernacular language, and as such can be confusing.

This relates particularly to what it means for a smart_ptr to "own" something. From the standard:

A shared_­ptr is said to be empty if it does not own a pointer.

This relates to the shared_ptr's idea of "ownership". From the perspective of a shared_ptr, it "owns" a pointer if it is created with a pointer value onto which it will call a deleter functor (given or default). Any shared_ptr which does not "own" a pointer in this way is empty.

However, this is distinct from what a human would consider "ownership". A human might say that if you provide a deleter to a shared_ptr that doesn't actually delete the object or otherwise free it up, then the shared_ptr doesn't really own the pointer.

But as far as the shared_ptr (and its various interfaces) is concerned, it owns that pointer, even if your given deleter isn't going to do anything to it.

As for how to handle your code specific code, it would be better to use unique_ptr and conditionally give it a value to destroy:

void foo(const MyClass* p) {
   unique_ptr<MyClass> u_p;
   if (p == nullptr) {
       u_p = std::make_unique<MyClass>("some", "parameters");
       p = u_p.get();
   }

   p->doSomething();
   ...
   p->doSomethingElse();
   // p must not be destroyed, its memory is managed somewhere else
}