| Document number: | P0033R1 |
| Date: | 2015-10-24 |
| Project: | Programming Language C++, Library Evolution Working Group |
| Reply-to: | Jonathan Wakely <cxx@kayari.org>, Peter Dimov <pdimov@pdimov.com> |
shared_from_this (revision 1)shared_from_this" definition.noexcept to weak_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.*thisshall be a subobject of an objecttof typeT. There shall be at least oneshared_ptrinstancepthat owns&t.
But depending on what happened at #2 it might be impossible to meet the postconditions:
Returns: Ashared_ptr<T>objectrthat 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_thisto indicate conformance to the new specification, including the newweak_from_thismembers.
Add a new paragraph before paragraph 1 of [util.smartptr.shared.const]:
In the constructor definitions below, enables
shared_from_thiswithp, for a pointerpof typeY*, means that ifYhas an unambiguous and accessible base class that is a specialization ofenable_shared_from_this([util.smartptr.enab]), thenremove_cv_t<Y>*shall be implicitly convertible toT*and 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));The assignment to the
weak_thismember is not atomic and conflicts with any potentially concurrent access to the same object (1.10).
Edit paragraph 4:
Effects: Constructs ashared_ptrobject that owns the pointerp. Enablesshared_from_thiswithp.
Edit paragraph 9:
Effects: Constructs ashared_ptrobject that owns the objectpand the deleterd. The first and second constructors enableshared_from_thiswithp. The second and fourth constructors shall use a copy ofato allocate memory for internal use.
Edit paragraph 29:
Effects: Equivalent toshared_ptr(r.release(), r.get_deleter())whenDis not a reference type, otherwiseshared_ptr(r.release(), ref(r.get_deleter())). Enablesshared_from_thiswith 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() noexcept; weak_ptr<T const> weak_from_this() const noexcept; 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_thisis 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.*thisshall be a subobject of an objecttof typeT. There shall be at least oneshared_ptrinstancepthat owns&t.
Returns: Ashared_ptr<T>objectrthat 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() noexcept;
weak_ptr<T const> weak_from_this() const noexcept;
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_ptrconstructors that create unique pointers can detect the presence of anenable_shared_from_thisbase and assign the newly createdshared_ptrto its__weak_thismember. — 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.