Consider the following code:
#include <iostream>
#include <memory>
using namespace std;
class T;
std::weak_ptr<T> wptr;
class T
{
public:
T() { }
~T() {
std::cout << "in dtor" << std::endl;
std::cout << (wptr.expired() ? "expired" : "not expired") << std::endl;
}
};
int main() {
{
auto ptr = std::make_shared<T>();
wptr = ptr;
std::cout << (wptr.expired() ? "expired" : "not expired") << std::endl;
}
return 0;
}
In this code, I was trying to find out if weak_ptr
s are expired in the objects destruction phase. It seems so. The output is:
not expired
in dtor
expired
I used gcc-5.1 with ideone.
Now, I have another problem. I couldn't find any documentation stating that this is the standard behavior. Is it guaranteed to work this way, always?
Now, I have another problem. I couldn't find any documentation stating that this is the standard behavior. Is it guaranteed to work this way, always?
No. Indeed, it's underspecified in the standard, as raised by LWG issue 2751.
The C++14 standard contains no language that guarantees the deleter run by a shared_ptr
will see all associated weak_ptr
instances as expired. For example, the standard doesn't appear to guarantee that the assertion in the following snippet won't fire:
std::weak_ptr<Foo> weak;
std::shared_ptr<Foo> strong{
new Foo,
[&weak] (Foo* f) {
assert(weak.expired());
delete f;
},
};
weak = strong;
strong.reset();
It seems clear that the intent is that associated weak_ptr
s are expired, because otherwise shared_ptr
deleters could resurrect a reference to an object that is being deleted.
Suggested fix: 23.11.3.2 [util.smartptr.shared.dest] should specify that the decrease in use_count()
caused by the destructor is sequenced before the call to the deleter or the call to delete p
.
The current wording for ~shared_ptr()
as linked above simply states that the deleter is invoked, with a non-normative note that the number of instances that share ownership is decreased.
While the intent is probably that weak.expired()
when the deleter is called, it's questionable to rely on this. It's really only reasonable to state with confidence that the shared_ptr
no longer shares ownership after it's been destroyed - asking that question during destruction is a bit odd.
Using make_shared like that will create an object with the default constructor you provided.
template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );
Constructs an object of type T and wraps it in a std::shared_ptr
using args as the parameter list for the constructor of T. The object
is constructed as if by the expression (std::make_shared)
After the anonymous scope in the main. The shared ptr will be deleted.
The object is destroyed and its memory deallocated when either of the
following happens:
the last remaining shared_ptr owning the object is destroyed;
(std::shared_ptr)
.
The destructor of shared_ptr decrements the number of shared owners of
the control block. If that counter reaches zero, the control block
calls the destructor of the managed object. The control block does not
deallocate itself until the std::weak_ptr counter reaches zero as
well. std::shared_ptr Implementation notes
This means that your object will call its destructor after the destruction of the last shared ptr has started.
The output:
not expired
in dtor
expired
is the expected behavior.
Not the standard itself but:
http://en.cppreference.com/w/cpp/memory/weak_ptr/expired
Checks whether the managed object has already been deleted. Equivalent
to use_count() == 0.
So it becomes a question of weather use_count
is set to 0 before or after deletion. Now there's a not on this in a draft of the standard:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3690.pdf [page 566 20.9.2.2.2]
~shared_ptr()
;
Effects:
- If
*this
is empty or shares ownership with another shared_ptr
instance (use_count() > 1), there are no side effects.
- Otherwise, if
*this
owns an object p
and a deleter d
, d(p)
is called.
- Otherwise,
*this
owns a pointer p
, and delete p
is called.
[Note: Since the destruction of *this
decreases the number of
instances that share ownership with *this
by one, after *this
has
been destroyed all shared_ptr
instances that shared ownership with
*this
will report a use_count()
that is one less than its previous
value. — end note]
A weak_ptr
expires when there are no more shared_ptr
s referring to the object.
When (immediately after) the last shared_ptr
stops referring to the object it's destroyed.
At this point there are no shared_ptr
s referring to it, so any weak_ptr
has expired.
The object's destructor is now invoked and if it has separate storage (i.e. wasn't created with make_shared
) its storage is deallocated.
The control block, where the reference count and raw pointer and delete function are stored, persists if there are any weak_ptr
s referring to it. When the last weak_ptr
stops referring to it, also the control block is destroyed and deallocated. I.e., shared_ptr
instances keep the object itself alive, along with its control block, while weak_ptr
instances keep the control block alive.