Document number: | P0033R0 |
Date: | 2015-09-23 |
Project: | Programming Language C++, Library Evolution Working Group |
Reply-to: | Jonathan Wakely <cxx@kayari.org>, Peter Dimov <pdimov@pdimov.com> |
shared_from_this
The class template enable_shared_from_this
is very weakly
specified, making it hard to reason about its behaviour in some situations and
risking implementation divergence.
This proposal provides a more precise specification and where the desired
behaviour is unclear recommends standardising the behaviour of
boost::enable_shared_from_this
.
In addition the weak_from_this
member functions of the Boost
version are proposed for inclusion in C++.
LWG issue 2529 shows the following program which has unspecified behaviour in C++14:
#include <memory>
using namespace std;
int main()
{
struct X : public enable_shared_from_this<X> { };
auto xraw = new X;
shared_ptr<X> xp1(xraw); // #1
{
shared_ptr<X> xp2(xraw, [](void*) { }); // #2
}
xraw->shared_from_this(); // #3
}
The standard says that #1 should set xraw->__weak_this
to
share ownership with xp1
.
It is unclear whether #2 should update it to share ownership with
xp2
instead. At #3 all the preconditions for calling
shared_from_this()
are met:
Requires:enable_shared_from_this<T>
shall be an accessible base class ofT
.*this
shall be a subobject of an objectt
of typeT
. There shall be at least oneshared_ptr
instancep
that owns&t
.
But depending on what happened at #2 it might be impossible to meet the postconditions:
Returns: Ashared_ptr<T>
objectr
that shares ownership withp
.
Postconditions:r.get() == this
.
If xraw->__weak_this
was updated at #2 then it is impossible to
meet the postcondition because the weak_ptr
is expired.
For the case where #2 does not update the weak_ptr
a different
example can be constructed that again makes it impossible to meet the
postcondition, by ensuring that whichever shared_ptr
shares
ownership with the weak_ptr
is made to release its ownership
before the call to shared_from_this()
.
The root of the problem is that enable_shared_from_this
is
underspecified, leaving the precise semantics up to implementors.
The aim of this proposal is to clearly specify the effects so that the
behaviour of the example above is predictable and portable.
The proposed specification is is a breaking change for the three major standard library implementations tested, however it is believed to be unlikely that any working code is relying on the current behaviour.
In addition to the improved specification for
enable_shared_from_this
two new member functions are proposed.
The weak_from_this
functions are directly analogous to the
existing shared_from_this
functions but they return a
weak_ptr
instead.
These new functions are a pure extension and have been provided in Boost since
the recent 1.58 release.
It is possible to obtain a weak_ptr
today via
weak_ptr<T>(shared_from_this())
but the proposed new
functions achieve the same thing more efficiently (with fewer reference count
modifications), they more clearly express the intent, and they can be used
in additional situations.
The proposed wording removes the preconditions on shared_from_this
so that it is now well-defined to call it on an object which is not owned by
any shared_ptr
, in which case shared_from_this
would
throw an exception.
weak_from_this().lock()
is a non-throwing alternative to
shared_from_this()
that returns an empty shared_ptr
when the object is not owned by any shared_ptr
.
This can be used in situations where the overhead of an exception is
undesirable and in environments that disable exceptions entirely.
weak_from_this
can also be used in the object's destructor, after
the weak_this
member has expired.
weak_from_this
can be used to get a weak_ptr
that
still shares ownership with any other weak_ptr
objects that still
exist after the last "strong" reference has been released by the last
shared_ptr
.
Finally, the proposed changes would also resolve LWG issue 2179. The improved specification clarifies that the behaviour of the example in the issue is undefined, as there is no sharing between the two instances.
The important design decision is whether or not a weak_ptr
member
that already shares ownership with one shared_ptr
should be
modified by subsequent constructions of unique shared_ptr
objects.
Existing practice is consistent across the Dinkumware, GNU and LLVM
implementations of std::shared_ptr
, with all three updating
the weak_ptr
member on subsequent constructions, however it
is not clear whether this is by design or simply an oversight due to not
considering the possibility (which is the case for the GNU implementation).
On the other hand, boost::shared_ptr
does not update the
weak_ptr
member and this is a deliberate choice that was
made in response to user feedback[1] and
consideration of the alternatives.
When asked about the above example Peter Dimov said:
"Based on our experience with boost::enable_shared_from_this, there are real-world cases where you very much want the above to work, and there are no cases in which you want it to not work."
"The first owner is generally what one wants returned from shared_from_this. There is one corner case here and it occurs when the object acquires a second owner when the first owner is already dead. The Boost implementation does replace the owner in this case.
In support of the argument that the first owner is the one that should be
associated with the weak_this
member, Peter also points out that
the code doing:
{
shared_ptr<X> xp2(xraw, [](void*) { }); // #2
}
may occur somewhere deep inside a library that only gets a raw pointer,
and that therefore it's much better if it doesn't break
xp1->shared_from_this()
,
because the owner of xp1
would not expect it.
The experience from Boost is persuasive, so the proposed change is to standardise the Boost behaviour, even though that requires all three standard library implementations to change their previous behaviour.
For the purposes of SG10, I recommend a feature-testing macro named__cpp_lib_enable_shared_from_this
to indicate conformance to the new specification, and if a separate macro is desired for the new members (which I don't think is necessary),__cpp_lib_weak_from_this
.
Add a new paragraph before paragraph 1 of [util.smartptr.shared.const]:
In the constructor definitions below, enables
shared_from_this
withp
, for a pointerp
of typeY*
, means that ifY
has an accessible base class that is a specialization ofenable_shared_from_this
([util.smartptr.enab]), then the constructor evaluates the statement:if (p != nullptr && p->weak_this.expired()) p->weak_this = shared_ptr<remove_cv_t<Y>>(*this, const_cast<remove_cv_t<Y>*>(p));
Edit paragraph 4:
Effects: Constructs ashared_ptr
object that owns the pointerp
. Enablesshared_from_this
withp
.
Edit paragraph 9:
Effects: Constructs ashared_ptr
object that owns the objectp
and the deleterd
. The first and second constructors enableshared_from_this
withp
. The second and fourth constructors shall use a copy ofa
to allocate memory for internal use.
Edit paragraph 29:
Effects: Equivalent toshared_ptr(r.release(), r.get_deleter())
whenD
is not a reference type, otherwiseshared_ptr(r.release(), ref(r.get_deleter()))
. Enablesshared_from_this
with the value that was returned byr.release()
.
Add two member functions and an exposition-only member to the class synopsis in [util.smartptr.enab]:
namespace std { template<class T> class enable_shared_from_this { protected: constexpr enable_shared_from_this() noexcept; enable_shared_from_this(enable_shared_from_this const&) noexcept; enable_shared_from_this& operator=(enable_shared_from_this const&) noexcept; ~enable_shared_from_this(); public: shared_ptr<T> shared_from_this(); shared_ptr<T const> shared_from_this() const; weak_ptr<T> weak_from_this(); weak_ptr<T const> weak_from_this() const; private: mutable weak_ptr<T> weak_this; // exposition only }; } // namespace std
Edit paragraph 4:
Effects:Constructs anValue-initializesenable_shared_from_this<T>
object.weak_this
.
Add a note after paragraph 5:
Returns:*this
.
[ Note:weak_this
is not changed. — end note ]
Remove the redundant destructor specification and paragraph 6:
~enable_shared_from_this
Effects: Destroys*this
.
Replace paragraphs 7, 8 and 9:
shared_ptr<T> shared_from_this();
shared_ptr<T const> shared_from_this() const;
Requires:enable_shared_from_this<T>
shall be an accessible base class ofT
.*this
shall be a subobject of an objectt
of typeT
. There shall be at least oneshared_ptr
instancep
that owns&t
.
Returns: Ashared_ptr<T>
objectr
that shares ownership withp
.
Returns:shared_ptr<T>(weak_this)
.
Add definitions for the new members after the replaced paragraph 9:
weak_ptr<T> weak_from_this();
weak_ptr<T const> weak_from_this() const;
Returns:weak_this
.
Remove the note in paragraphs 10 and 11:
[ Note: A possible implementation is shown below:
template<class T> class enable_shared_from_this { private: weak_ptr<T> __weak_this; protected: constexpr enable_shared_from_this() : __weak_this() { } enable_shared_from_this(enable_shared_from_this const &) { } enable_shared_from_this& operator=(enable_shared_from_this const &) { return *this; } ~enable_shared_from_this() { } public: shared_ptr<T> shared_from_this() { return shared_ptr<T>(__weak_this); } shared_ptr<T const> shared_from_this() const { return shared_ptr<T const>(__weak_this); } };
Theshared_ptr
constructors that create unique pointers can detect the presence of anenable_shared_from_this
base and assign the newly createdshared_ptr
to its__weak_this
member. — end note ]
Boost's boost::enable_shared_from_this
has done the
expired()
check for years, and has provided
weak_from_this
since the Boost 1.58.0 release.
The libstdc++ development sources were recently altered to do the expired
check.
Thanks to Howard Hinnant for his comments on the issue.
[1] Ticket #2584: boost::enable_shared_from_this + boost.python library, Boost Trac, Nicolas Lelong, 2008-12-12.
[2] [boost] enable_shared_from_this::weak_from_this() request, Boost mailing list, Marat Abrarov, 2011-06-11.