What is the difference between an empty and a null std::shared_ptr in C++?
It's a weird corner of shared_ptr
behavior. It has a constructor that allows you to make a shared_ptr
that owns something and points to something else:
template< class Y >
shared_ptr( const shared_ptr<Y>& r, T *ptr );
The shared_ptr
constructed using this constructor shares ownership with r
, but points to whatever ptr
points to (i.e., calling get()
or operator->()
will return ptr
). This is handy for cases where ptr
points to a subobject (e.g., a data member) of the object owned by r
.
The page you linked calls a shared_ptr
that owns nothing empty, and a shared_ptr
that points to nothing (i.e., whose get() == nullptr
) null. (Empty is used in this sense by the standard; null isn't.) You can construct a null-but-not-empty shared_ptr
, but it won't be very useful. An empty-but-not-null shared_ptr
is essentially a non-owning pointer, which can be used to do some weird things like passing a pointer to something allocated on the stack to a function expecting a shared_ptr
(but I'd suggest punching whoever put shared_ptr
inside the API first).
boost::shared_ptr
also has this constructor, which they call the aliasing constructor.
std::shared_ptr which is null but not empty
Yes, the "null but not empty" shared_ptr
will keep the object alive because it shares ownership with the shared_ptr
it was constructed from. All shared_ptr
s that share ownership with each other contribute to an atomic reference count stored in the control block, and only when this reference count hits zero is the owned object destroyed.
For standardese, see [util.smartptr.shared.const]/14:
template<class Y> shared_ptr(const shared_ptr<Y>& r, element_type* p) noexcept;
Constructs ashared_ptr
instance that storesp
and shares ownership withr
There are no constraints specified on the value of p
; it may therefore be any valid pointer value, including null or even a past-the-end pointer (though I'm not sure why you'd want to do this).
Then see [util.smartptr.shared.dest]/(1.1):
If
*this
is empty or shares ownership with anothershared_ptr
instance (use_count() > 1
), there are no side effects.
In other words, when ps
is destroyed, it still shares ownership with p
, so the object is not destroyed yet.
The resulting object is not really unreachable in the usual sense, since it is still possible to destroy it. You just can't do anything else with it.
Is it correct to return null shared_ptr?
Is it correct to return shared_ptr implicitly initialized with nullptr like in upper example?
Yes, it is correct to initialize shared_ptr
with nullptr
. It is also correct to assign nullptr
to shared_ptr
.
Or should I return shared_ptr default constructed instead?
You can do this in both ways:
returning
shared_ptr
initialized withnullptr
return shared_ptr<Object>(nullptr);
returning
shared_ptr
default constructed.return shared_ptr<Object>();
Both ways are correct and both have the same effect. You can use whatever way you want.
What in case it would be weak_ptr? What is proper way to check that empty weak_ptr has been returned? by weak_ptr::expired function or are there other ways?
weak_ptr
becomes nullptr
(expires) whenever the last shared_ptr
associated with object is destroyed.
The proper way to work with weak_ptr
is to convert it to shared_ptr
with lock method, and then to work with created shared_ptr
. In that case your weak_ptr
will no expire until you have that new shared_ptr
. If you don't convert weak_ptr
into shared_ptr
, your weak_ptr
may expire at any moment.
And yes, before working with newly created shared_ptr
, you must check that it isn't null, because weak_ptr
may had been expired before you created shared_ptr
with lock
method.
std::weak_ptr<Object> Storage::findObject();
...
std::weak_ptr <Object> weak = Storage::findObject();
std::shared_ptr<Object> shared = weak.lock();
if (shared) // check that weak was not expired when we did "shared = weak.lock()"
{
// do something with shared, it will not expire.
}
How to test whether a shared_ptr is empty or owns nothing
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;
}
Why does one need a null shared_ptr and how can it be used?
There is no need to use that hack to get a null (empty) shared_ptr
. Simply use the default constructor:
std::shared_ptr<Investment> pInv; // starts null
To assign a pointer to a shared_ptr
, either do it at construction time:
std::shared_ptr<Investment> pInt(new Investment);
// not allowed due to explicit annotation on constructor:
// std::shared_ptr<Investment> pInt = new Investment;
Or use the .reset()
function:
pInt.reset(new Investment);
It's possible that the author of that article may have intended to provide a custom deleter (getRidOfInvestment
). However, the deleter function is reset when .reset()
is called, or when otherwise the inner pointer is changed. If you want a custom deleter, you must pass it to .reset()
upon creation of the shared_ptr
.
One pattern you might want to use to make this more foolproof is a custom creation function:
class Investment {
protected:
Investment();
// ...
public:
static shared_ptr<Investment> create();
};
shared_ptr<Investment> Investment::create() {
return shared_ptr<Investment>(new Investment, getRidOfInvestment);
}
Later:
shared_ptr<Investment> pInv = Investment::create();
This ensures you will always have the correct destructor function attached to the shared_ptr
s created from Investment
s.
Semantic of empty shared_ptr
The semantics are:
- If you default-construct a shared pointer, or construct one from
nullptr_t
, it's empty; that is, it doesn't own any pointer. - If you construct one from a raw pointer, it takes ownership of that pointer, whether or not it's null. I guess that's done for the reason you mention (avoiding a runtime check), but I can only speculate about that.
So your example isn't empty; it owns a null pointer.
Does it make sense to check for nullptr in custom deleter of shared_ptr?
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.
Is there a recommended way to test if a smart pointer is null?
Is there a difference between doing
std::shared_ptr<int> p;
if (!p) { // method 1 }
if (p == nullptr) { // method 2 }
No, there's no difference. Either of that operations has a properly defined overload.
Another equivalent would be
if(p.get() == nullptr)
Related Topics
If Temporaries Are Implicitly Non-Modifiable, How Does This Work
How to Securely Disconnect an Asio Ssl Socket
Why "Initializer-String for Array of Chars Is Too Long" Compiles Fine in C & Not in C++
Does Case-Switch Work Like This
Why Does 'Int ;' Compile Fine in C, But Not in C++
Lifetime of Lambda Objects in Relation to Function Pointer Conversion
How to Select a Random Element in Std::Set
Std::Vector of Std::Vectors Contiguity
C++ Abstract Class Without Pure Virtual Functions
Generate Include File Name in a MACro
Sizeof in C++ Showing String Size One Less
C++ Error, Undefined Reference Class
What Is Linux's Native Gui API
In Cmake, How to Test If the Compiler Is Clang
Easiest Way of Using Min Priority Queue with Key Update in C++
Why Would Anyone Use Set Instead of Unordered_Set
Passing a Pointer Representing a 2D Array to a Function in C++