shared_ptr::weak_type (original) (raw)
Document number: P0163R0
Date: 2015-10-23
Project: ISO JTC1/SC22/WG21, Programming Language C++
Audience: Library Evolution Working Group
Reply to: Arthur O'Dwyer arthur.j.odwyer@gmail.com
1. Introduction
2. Problem: Generic programming and weak_ptr
3. One more motivating example
4. Solution
5. Alternatives rejected at Kona
6. Proposed wording
6a. Proposed wording for C++17 [util.smartptr.shared]
6b. Proposed wording for Library Fundamentals V2 [memory.smartptr.shared]
7. References
8. Thanks
1. Introduction
This paper identifies a minor inconvenience in the design of shared_ptr, in that it is impossible to create a weak_ptr from a shared_ptrwithout explicitly spelling out the type of the weak_ptr, which impedes generic programming. N4537 [N4537] proposed to solve this problem by introducing the member function shared_ptr::unlock(), but this idea was rejected by LEWG at Kona. LEWG strongly supported the idea of providing a member typedef shared_ptr::weak_type, so that's what the present paper proposes, for both C++17 and Library Fundamentals V2.
2. Problem: Generic programming and weak_ptr
Given an arbitrary shared_ptr, write code to "weaken" the object into a weak_ptr with the same shared ownership and stored pointer.
template<class ObjectType>
void register_observers(ObjectType& obj)
{
auto sptr = obj.get_shared_ptr(); // for example, via shared_from_this
auto wptr = weak_ptr<ObjectType>(sptr);
sptr.reset(); // drop the strong reference as soon as possible
register_observer_1(wptr);
register_observer_2(wptr);
}Unfortunately, this code is not perfectly generic: it will fail (or at least do the wrong thing) in the case that obj.get_shared_ptr()returns something other than shared_ptr<ObjectType>. For example, it might return shared_ptr<BaseClassOfObjectType>. Or, in an advanced scenario, we might want to be able to "drop in" a replacement class custom_shared_ptr instead of the standardshared_ptr, in which case we would also have to replaceweak_ptr in the above code with custom_weak_ptr.
Notice that at least two values of "custom_shared_ptr" exist in the wild:std::experimental::shared_ptr in Library Fundamentals [N4077], and boost::shared_ptr. Neither of these implementations currently provide weak_type. This paper proposes addingweak_type to both C++17 and Library Fundamentals V2.
Notice that the only place in the code sample where an explicit type is used, is in the line concerned with "weakening" sptr. "Weakening" a shared_ptr into a weak_ptr is not an operation that ought to force explicit types into otherwise generic code.
3. One more motivating example
A real-world example of this pattern cropped up recently when Arthur attempted to implement a "task-based programming" library with task cancellation, as described in Sean Parent's "Better Code: Concurrency" [Parent]. In this library, the TaskControlBlock is the central concept; a Future is simply a thin wrapper around astd::shared_ptr<TaskControlBlock>, and aCancellablePackagedTask is a thin wrapper around astd::weak_ptr<TaskControlBlock>. When the last Future referring to a TaskControlBlockis destroyed, the TaskControlBlock itself (if the task has not yet begun to execute) may be destroyed. The implementation ofEnqueueTask in this system looks like this:
template<typename T>
inline std::weak_ptr<T> Unlock(const std::shared_ptr<T>& sptr)
{
return std::weak_ptr<T>(sptr);
}
template<typename Func>
auto EnqueueTask(Func&& func) -> Future<decltype(func())>
{
using T = decltype(func());
auto sptr = std::make_shared<TaskControlBlock<T>>();
auto task = [
func = std::forward<Func>(func),
wptr = Unlock(sptr)
]() {
Promise<T> px { wptr };
px.set_value(func());
};
GlobalTaskPool.submit(task);
Future<T> fx { std::move(sptr) };
return fx;
}Notice the use of the hand-coded free function Unlock(sptr); that's a workaround for the unwieldy expression std::weak_ptr<TaskControlBlock<T>>(sptr).
4. Solution
We propose adding one new typedef to the standard library.shared_ptr gains a member typedef
using weak_type = weak_ptr;This allows us to rewrite the "problem" code above in generic style, naming no types explicitly, as:
template<class ObjectType>
void register_observers(ObjectType& obj) {
auto sptr = obj.get_shared_ptr(); // for example, via shared_from_this
auto wptr = typename decltype(sptr)::weak_type{sptr};
sptr.reset(); // drop the strong reference as soon as possible
register_observer_1(wptr);
register_observer_2(wptr);
}and the other example becomes:
auto task = [
func = std::forward<Func>(func),
wptr = typename decltype(sptr)::weak_type{sptr}
]() {
Promise<T> px { wptr };
px.set_value(func());
};The author is not happy with the proposed style's verbosity, but at least this does solve the generic-programming problem. Also, this proposal avoids introducing any further asymmetry (as would be the case if we introduced a free functionstd::weaken without std::strengthen).
5. Alternatives rejected at Kona
At Kona (October 2015), the following was proposed and/or straw-polled as N4537:[Issues]
Do we want sptr.unlock() by that name, as proposed in N4537?
(consensus was against)
Do we want that functionality at all, e.g. under the name std::weaken(sptr)?
SF F N A SA
1 1 6 6 1
Should we provide shared_ptr<T>::weak_type?
SF F N A SA
0 10 5 0 1
Therefore, this paper proposes only the member typedef that garnered support.
6. Proposed wording
6a. Proposed wording for C++17
The wording in this section is relative to WG21 draft N4527 [N4527], that is, the draft of the C++17 standard.
20.8.2.2 Class template shared_ptr [util.smartptr.shared]
Edit paragraph 1 as follows.
` template class shared_ptr {
public:
typedef T element_type;
typedef weak_ptr weak_type;// 20.8.2.2.1, constructors:
`
6b. Proposed wording for Library Fundamentals V2
The wording in this section is relative to WG21 draft N4529 [N4529], that is, the draft of Library Fundamentals V2.
8.2.1 Class template shared_ptr [memory.smartptr.shared]
Edit paragraph 1 as follows.
template class shared_ptr { public: typedef typename remove_extent_t<T> element_type; typedef weak_ptr<T> weak_type; // 8.2.1.1, shared_ptr constructors
7. References
[Issues]
"Bug 103: Adding Symmetry Between shared_ptr and weak_ptr" (October 2015).
https://issues.isocpp.org/show_bug.cgi?id=103.
[N4077]
Jonathan Wakely. "Experimental shared_ptr for Library Fundamentals TS" (June 2014).
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4077.html.
[N4527]
"Working Draft, Standard for Programming Language C++" (May 2015).
http://isocpp.org/files/papers/n4527.pdf.
[N4529]
"Working Draft, C++ Extensions for Library Fundamentals, Version 2" (May 2015).
http://isocpp.org/files/papers/n4529.pdf.
[N4537]
Arthur O'Dwyer. "Adding Symmetry Between shared_ptr and weak_ptr" (May 2015).
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4537.html.
[Parent]
"Better Code: Concurrency" (February 2015).
https://github.com/sean-parent/sean-parent.github.io/wiki/Papers-and-Presentations.
8. Thanks
Thanks to LEWG for feedback on N4537, and to Jonathan Wakely for feedback and for the correct spelling of his surname.