Skip to content

Commit fc587f3

Browse files
oldnewthingjonwis
andauthored
Allow delegates to be created with weak reference + lambda (#1372)
We have found that a very common pattern for event handlers is to capture a weak reference into a lambda, and in the event handler, try to upgrade the weak reference to a strong one, and if so, do some work: ```cpp widget.Closed([weak = get_weak(), data](auto&& sender, auto&& args) { if (auto strongThis = weak.get()) { strongThis->do_all_the_things(data); } }); ``` This commit extends the existing delegate constructors to permit a `winrt::weak_ref` + lambda (or `std::weak_ptr` + lambda), which simplifies the above to ```cpp widget.Closed({ get_weak(), [this, data](auto&& sender, auto&& args) { do_all_the_things(data); } }); ``` ## Implementation notes A lambda and pointer to member function are hard to distinguish in a template parameter list. In theory, we could use SFINAE or partial specialization, but a simpler solution is to distinguish the two inside the body of the constructor, via `std::is_member_function_pointer_v`. The `com_ptr` and `shared_ptr` variants of the test were unified, since I found myself editing two nearly identical tests. Fixes #1371 Co-authored-by: Jon Wiswall <[email protected]>
1 parent 912aa47 commit fc587f3

File tree

3 files changed

+106
-86
lines changed

3 files changed

+106
-86
lines changed

cppwinrt/code_writers.h

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2488,9 +2488,9 @@ struct WINRT_IMPL_EMPTY_BASES produce_dispatch_to_overridable<T, D, %>
24882488
template <typename F> %(F* function);
24892489
template <typename O, typename M> %(O* object, M method);
24902490
template <typename O, typename M> %(com_ptr<O>&& object, M method);
2491-
template <typename O, typename M> %(weak_ref<O>&& object, M method);
2491+
template <typename O, typename LM> %(weak_ref<O>&& object, LM&& lambda_or_method);
24922492
template <typename O, typename M> %(std::shared_ptr<O>&& object, M method);
2493-
template <typename O, typename M> %(std::weak_ptr<O>&& object, M method);
2493+
template <typename O, typename LM> %(std::weak_ptr<O>&& object, LM&& lambda_or_method);
24942494
auto operator()(%) const;
24952495
};
24962496
)";
@@ -2566,16 +2566,22 @@ struct WINRT_IMPL_EMPTY_BASES produce_dispatch_to_overridable<T, D, %>
25662566
%([o = std::move(object), method](auto&&... args) { return ((*o).*(method))(args...); })
25672567
{
25682568
}
2569-
template <%> template <typename O, typename M> %<%>::%(weak_ref<O>&& object, M method) :
2570-
%([o = std::move(object), method](auto&&... args) { if (auto s = o.get()) { ((*s).*(method))(args...); } })
2569+
template <%> template <typename O, typename LM> %<%>::%(weak_ref<O>&& object, LM&& lambda_or_method) :
2570+
%([o = std::move(object), lm = std::forward<LM>(lambda_or_method)](auto&&... args) { if (auto s = o.get()) {
2571+
if constexpr (std::is_member_function_pointer_v<LM>) ((*s).*(lm))(args...);
2572+
else lm(args...);
2573+
} })
25712574
{
25722575
}
25732576
template <%> template <typename O, typename M> %<%>::%(std::shared_ptr<O>&& object, M method) :
25742577
%([o = std::move(object), method](auto&&... args) { return ((*o).*(method))(args...); })
25752578
{
25762579
}
2577-
template <%> template <typename O, typename M> %<%>::%(std::weak_ptr<O>&& object, M method) :
2578-
%([o = std::move(object), method](auto&&... args) { if (auto s = o.lock()) { ((*s).*(method))(args...); } })
2580+
template <%> template <typename O, typename LM> %<%>::%(std::weak_ptr<O>&& object, LM&& lambda_or_method) :
2581+
%([o = std::move(object), lm = std::forward<LM>(lambda_or_method)](auto&&... args) { if (auto s = o.lock()) {
2582+
if constexpr (std::is_member_function_pointer_v<LM>) ((*s).*(lm))(args...);
2583+
else lm(args...);
2584+
} })
25792585
{
25802586
}
25812587
template <%> auto %<%>::operator()(%) const
@@ -2652,16 +2658,22 @@ struct WINRT_IMPL_EMPTY_BASES produce_dispatch_to_overridable<T, D, %>
26522658
%([o = std::move(object), method](auto&&... args) { return ((*o).*(method))(args...); })
26532659
{
26542660
}
2655-
template <typename O, typename M> %::%(weak_ref<O>&& object, M method) :
2656-
%([o = std::move(object), method](auto&&... args) { if (auto s = o.get()) { ((*s).*(method))(args...); } })
2661+
template <typename O, typename LM> %::%(weak_ref<O>&& object, LM&& lambda_or_method) :
2662+
%([o = std::move(object), lm = std::forward<LM>(lambda_or_method)](auto&&... args) { if (auto s = o.get()) {
2663+
if constexpr (std::is_member_function_pointer_v<LM>) ((*s).*(lm))(args...);
2664+
else lm(args...);
2665+
} })
26572666
{
26582667
}
26592668
template <typename O, typename M> %::%(std::shared_ptr<O>&& object, M method) :
26602669
%([o = std::move(object), method](auto&&... args) { return ((*o).*(method))(args...); })
26612670
{
26622671
}
2663-
template <typename O, typename M> %::%(std::weak_ptr<O>&& object, M method) :
2664-
%([o = std::move(object), method](auto&&... args) { if (auto s = o.lock()) { ((*s).*(method))(args...); } })
2672+
template <typename O, typename LM> %::%(std::weak_ptr<O>&& object, LM&& lambda_or_method) :
2673+
%([o = std::move(object), lm = std::forward<LM>(lambda_or_method)](auto&&... args) { if (auto s = o.lock()) {
2674+
if constexpr (std::is_member_function_pointer_v<LM>) ((*s).*(lm))(args...);
2675+
else lm(args...);
2676+
} })
26652677
{
26662678
}
26672679
inline auto %::operator()(%) const

strings/base_delegate.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,11 @@ namespace winrt::impl
180180
{
181181
}
182182

183-
template <typename O, typename M> delegate_base(winrt::weak_ref<O>&& object, M method) :
184-
delegate_base([o = std::move(object), method](auto&& ... args) { if (auto s = o.get()) { ((*s).*(method))(args...); } })
183+
template <typename O, typename LM> delegate_base(winrt::weak_ref<O>&& object, LM&& lambda_or_method) :
184+
delegate_base([o = std::move(object), lm = std::forward<LM>(lambda_or_method)](auto&&... args) { if (auto s = o.get()) {
185+
if constexpr (std::is_member_function_pointer_v<LM>) ((*s).*(lm))(args...);
186+
else lm(args...);
187+
}})
185188
{
186189
}
187190

@@ -190,8 +193,11 @@ namespace winrt::impl
190193
{
191194
}
192195

193-
template <typename O, typename M> delegate_base(std::weak_ptr<O>&& object, M method) :
194-
delegate_base([o = std::move(object), method](auto&& ... args) { if (auto s = o.lock()) { ((*s).*(method))(args...); } })
196+
template <typename O, typename LM> delegate_base(std::weak_ptr<O>&& object, LM&& lambda_or_method) :
197+
delegate_base([o = std::move(object), lm = std::forward<LM>(lambda_or_method)](auto&&... args) { if (auto s = o.lock()) {
198+
if constexpr (std::is_member_function_pointer_v<LM>) ((*s).*(lm))(args...);
199+
else lm(args...);
200+
}})
195201
{
196202
}
197203

test/old_tests/UnitTests/delegate_weak_strong.cpp

Lines changed: 74 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -8,55 +8,85 @@ using namespace Windows::Foundation::Collections;
88

99
namespace
1010
{
11-
bool destroyed{};
12-
int strong_count{};
13-
int weak_count{};
11+
struct Counters
12+
{
13+
bool destroyed{};
14+
int strong_count{};
15+
int weak_count{};
16+
int weak_lambda_count{};
17+
18+
bool is_count(int value)
19+
{
20+
return strong_count == value && weak_count == value && weak_lambda_count == value;
21+
}
22+
};
1423

1524
template <typename Sender, typename Args>
1625
struct Object : implements<Object<Sender, Args>, IInspectable>
1726
{
27+
std::shared_ptr<Counters> m_counters;
28+
29+
Object(std::shared_ptr<Counters> const& counters) : m_counters(counters) {}
30+
31+
static auto make(std::shared_ptr<Counters> const& counters)
32+
{
33+
return make_self<Object>(counters);
34+
}
35+
36+
auto strong() { return this->get_strong(); }
37+
auto weak() { return this->get_weak(); }
38+
1839
~Object()
1940
{
20-
destroyed = true;
41+
m_counters->destroyed = true;
2142
}
2243

2344
void StrongHandler(Sender const&, Args const&)
2445
{
25-
++strong_count;
46+
REQUIRE(!m_counters->destroyed);
47+
++m_counters->strong_count;
2648
}
2749

2850
void WeakHandler(Sender const&, Args const&)
2951
{
30-
++weak_count;
52+
REQUIRE(!m_counters->destroyed);
53+
++m_counters->weak_count;
3154
}
3255
};
3356

3457
template <typename Sender, typename Args>
3558
struct ObjectStd : std::enable_shared_from_this<ObjectStd<Sender, Args>>
3659
{
60+
std::shared_ptr<Counters> m_counters;
61+
62+
ObjectStd(std::shared_ptr<Counters> const& counters) : m_counters(counters) {}
63+
64+
static auto make(std::shared_ptr<Counters> const& counters)
65+
{
66+
return std::make_shared<ObjectStd>(counters);
67+
}
68+
69+
auto strong() { return this->shared_from_this(); }
70+
auto weak() { return this->weak_from_this(); }
71+
3772
~ObjectStd()
3873
{
39-
destroyed = true;
74+
m_counters->destroyed = true;
4075
}
4176

4277
void StrongHandler(Sender const&, Args const&)
4378
{
44-
++strong_count;
79+
++m_counters->strong_count;
4580
}
4681

4782
void WeakHandler(Sender const&, Args const&)
4883
{
49-
++weak_count;
84+
++m_counters->weak_count;
5085
}
5186
};
5287

5388
struct ReturnObject : implements<ReturnObject, IInspectable>
5489
{
55-
~ReturnObject()
56-
{
57-
destroyed = true;
58-
}
59-
6090
int Handler(int a, int b)
6191
{
6292
return a + b;
@@ -65,91 +95,63 @@ namespace
6595

6696
struct ReturnObjectStd : std::enable_shared_from_this<ReturnObjectStd>
6797
{
68-
~ReturnObjectStd()
69-
{
70-
destroyed = true;
71-
}
72-
7398
int Handler(int a, int b)
7499
{
75100
return a + b;
76101
}
77102
};
78103

79-
template <typename Delegate, typename Sender, typename Args>
80-
void test_delegate_winrt()
104+
template <typename Recipient, typename Delegate>
105+
void test_delegate_pattern()
81106
{
82-
auto object = make_self<Object<Sender, Args>>();
83-
84-
Delegate strong{ object->get_strong(), &Object<Sender, Args>::StrongHandler };
85-
Delegate weak{ object->get_weak(), &Object<Sender, Args>::WeakHandler };
107+
auto counters = std::make_shared<Counters>();
108+
auto object = Recipient::make(counters);
86109

87-
destroyed = false;
88-
strong_count = 0;
89-
weak_count = 0;
110+
Delegate strong{ object->strong(), &Recipient::StrongHandler};
111+
Delegate weak{ object->weak(), &Recipient::WeakHandler };
112+
Delegate weak_lambda{ object->weak(),[counters](auto&&, auto&&) {
113+
REQUIRE(!counters->destroyed);
114+
++counters->weak_lambda_count;
115+
} };
90116

91-
// Both weak and strong handlers
117+
// All handlers are active at this point
92118
strong({}, {});
93119
weak({}, {});
94-
REQUIRE(strong_count == 1);
95-
REQUIRE(weak_count == 1);
120+
weak_lambda({}, {});
121+
REQUIRE(counters->is_count(1));
96122

97123
// Local 'object' strong reference is released
98124
object = nullptr;
99125

100-
// Still both since strong handler keeps object alive
126+
// Still invoked since strong handler keeps object alive
101127
strong({}, {});
102128
weak({}, {});
103-
REQUIRE(strong_count == 2);
104-
REQUIRE(weak_count == 2);
129+
weak_lambda({}, {});
130+
REQUIRE(counters->is_count(2));
105131

106-
// ~Object is called since the strong delegate is destroyed
107-
REQUIRE(!destroyed);
132+
// ~Recipient is called since the strong delegate is destroyed
133+
REQUIRE(!counters->destroyed);
108134
strong = nullptr;
109-
REQUIRE(destroyed);
135+
REQUIRE(counters->destroyed);
110136

111137
// Weak delegate remains but no longer fires
112-
REQUIRE(weak_count == 2);
138+
// Strong delegate shouldn't fire either
139+
REQUIRE(counters->is_count(2));
113140
weak({}, {});
114-
REQUIRE(weak_count == 2);
141+
weak_lambda({}, {});
142+
REQUIRE(counters->is_count(2));
115143
}
116144

117145
template <typename Delegate, typename Sender, typename Args>
118-
void test_delegate_std()
146+
void test_delegate_winrt()
119147
{
120-
auto object = std::make_shared<ObjectStd<Sender, Args>>();
121-
122-
Delegate strong{ object->shared_from_this(), &ObjectStd<Sender, Args>::StrongHandler };
123-
Delegate weak{ object->weak_from_this(), &ObjectStd<Sender, Args>::WeakHandler };
124-
125-
destroyed = false;
126-
strong_count = 0;
127-
weak_count = 0;
128-
129-
// Both weak and strong handlers
130-
strong({}, {});
131-
weak({}, {});
132-
REQUIRE(strong_count == 1);
133-
REQUIRE(weak_count == 1);
134-
135-
// Local 'object' strong reference is released
136-
object = nullptr;
137-
138-
// Still both since strong handler keeps object alive
139-
strong({}, {});
140-
weak({}, {});
141-
REQUIRE(strong_count == 2);
142-
REQUIRE(weak_count == 2);
143-
144-
// ~Object is called since the strong delegate is destroyed
145-
REQUIRE(!destroyed);
146-
strong = nullptr;
147-
REQUIRE(destroyed);
148+
test_delegate_pattern<Object<Sender, Args>, Delegate>();
149+
}
148150

149-
// Weak delegate remains but no longer fires
150-
REQUIRE(weak_count == 2);
151-
weak({}, {});
152-
REQUIRE(weak_count == 2);
151+
template <typename Delegate, typename Sender, typename Args>
152+
void test_delegate_std()
153+
{
154+
test_delegate_pattern<ObjectStd<Sender, Args>, Delegate>();
153155
}
154156

155157
template <typename Delegate, typename Sender, typename Args>

0 commit comments

Comments
 (0)