I've seen some code that uses std::shared_ptr
with a custom deleter that test the argument for nullptr, for example, MyClass
which has a close()
method and is constructed with some CreateMyClass
:
auto pMyClass = std::shared_ptr<MyClass>(CreateMyClass(),
[](MyClass* ptr)
{
if(ptr)
ptr->close();
});
Does it make sense to test ptr
for null-ness in the deleter?
Can this happen? how?
A null shared_ptr does serve the same purpose as a raw null pointer. It might indicate the non-availability of data. However, for the most part, there is no reason for a null shared_ptr to possess a control block or a managed nullptr .
Use unique_ptr when you want a single pointer to an object that will be reclaimed when that single pointer is destroyed. Use shared_ptr when you want multiple pointers to the same resource.
Nullability - a scoped_ptr or unique_ptr can be null, a value object can never be. Polymorphism - a value object is always exactly its static type, but you can substitute in different derived types for a unique_ptr. The previously-held object is automatically destroyed when you do this.
Empty shared_ptr can be constructed with default constructor or with constructor that takes nullptr . Non-empty null shared_ptr has control block that can be shared with other shared_ptr s. Copy of non-empty null shared_ptr is shared_ptr that shares the same control block as original shared_ptr so use count is not 0.
The constructor std::shared_ptr<T>::shared_ptr(Y*p)
has the requirement that delete p
is a valid operation. This is a valid operation when p
equals nullptr
.
The constructor std::shared_ptr<T>::shared_ptr(Y*p, Del del)
has the requirement that del(p)
is a valid operation.
If your custom deleter cannot handle p
being equal to nullptr
then it is not valid to pass a null p
in the constructor of shared_ptr
.
The constructor you offer as an example can be better presented, thus:
#include <memory>
struct MyClass {
void open() {
// note - may throw
};
void close() noexcept {
// pre - is open
}
};
struct Closer
{
void operator()(MyClass* p) const noexcept
{
p->close();
delete p; // or return to pool, etc
}
};
auto CreateMyClass() -> std::unique_ptr<MyClass, Closer>
{
// first construct with normal deleter
auto p1 = std::make_unique<MyClass>();
// in case this throws an exception.
p1->open();
// now it's open, we need a more comprehensive deleter
auto p = std::unique_ptr<MyClass, Closer> { p1.release(), Closer() };
return p;
}
int main()
{
auto sp = std::shared_ptr<MyClass>(CreateMyClass());
}
Note that it is now not possible for the shared_ptr to own a null object.
Yes, it makes sense actually. Suppose CreateMyClass
returns nullptr
. Reference count of pMyClass
(use_count
) becomes 1
. When pMyClass
will be destroyed, following will happens:
If
*this
owns an object and it is the lastshared_ptr
owning it, the object is destroyed through the owned deleter.
So if custom deleter dereferencing a pointer that holded by shared_ptr (ptr->close()
in your code) then it should take care of nullptr checking.
Notice that empty shared_ptr is not the same as null shared_ptr.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With