зеркало из https://github.com/microsoft/cppwinrt.git
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 <jonwis@microsoft.com>
This commit is contained in:
Родитель
912aa47ff4
Коммит
fc587f31f9
|
@ -2488,9 +2488,9 @@ struct WINRT_IMPL_EMPTY_BASES produce_dispatch_to_overridable<T, D, %>
|
||||||
template <typename F> %(F* function);
|
template <typename F> %(F* function);
|
||||||
template <typename O, typename M> %(O* object, M method);
|
template <typename O, typename M> %(O* object, M method);
|
||||||
template <typename O, typename M> %(com_ptr<O>&& object, M method);
|
template <typename O, typename M> %(com_ptr<O>&& object, M method);
|
||||||
template <typename O, typename M> %(weak_ref<O>&& object, M method);
|
template <typename O, typename LM> %(weak_ref<O>&& object, LM&& lambda_or_method);
|
||||||
template <typename O, typename M> %(std::shared_ptr<O>&& object, M method);
|
template <typename O, typename M> %(std::shared_ptr<O>&& object, M method);
|
||||||
template <typename O, typename M> %(std::weak_ptr<O>&& object, M method);
|
template <typename O, typename LM> %(std::weak_ptr<O>&& object, LM&& lambda_or_method);
|
||||||
auto operator()(%) const;
|
auto operator()(%) const;
|
||||||
};
|
};
|
||||||
)";
|
)";
|
||||||
|
@ -2566,16 +2566,22 @@ struct WINRT_IMPL_EMPTY_BASES produce_dispatch_to_overridable<T, D, %>
|
||||||
%([o = std::move(object), method](auto&&... args) { return ((*o).*(method))(args...); })
|
%([o = std::move(object), method](auto&&... args) { return ((*o).*(method))(args...); })
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
template <%> template <typename O, typename M> %<%>::%(weak_ref<O>&& object, M method) :
|
template <%> template <typename O, typename LM> %<%>::%(weak_ref<O>&& object, LM&& lambda_or_method) :
|
||||||
%([o = std::move(object), method](auto&&... args) { if (auto s = o.get()) { ((*s).*(method))(args...); } })
|
%([o = std::move(object), lm = std::forward<LM>(lambda_or_method)](auto&&... args) { if (auto s = o.get()) {
|
||||||
|
if constexpr (std::is_member_function_pointer_v<LM>) ((*s).*(lm))(args...);
|
||||||
|
else lm(args...);
|
||||||
|
} })
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
template <%> template <typename O, typename M> %<%>::%(std::shared_ptr<O>&& object, M method) :
|
template <%> template <typename O, typename M> %<%>::%(std::shared_ptr<O>&& object, M method) :
|
||||||
%([o = std::move(object), method](auto&&... args) { return ((*o).*(method))(args...); })
|
%([o = std::move(object), method](auto&&... args) { return ((*o).*(method))(args...); })
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
template <%> template <typename O, typename M> %<%>::%(std::weak_ptr<O>&& object, M method) :
|
template <%> template <typename O, typename LM> %<%>::%(std::weak_ptr<O>&& object, LM&& lambda_or_method) :
|
||||||
%([o = std::move(object), method](auto&&... args) { if (auto s = o.lock()) { ((*s).*(method))(args...); } })
|
%([o = std::move(object), lm = std::forward<LM>(lambda_or_method)](auto&&... args) { if (auto s = o.lock()) {
|
||||||
|
if constexpr (std::is_member_function_pointer_v<LM>) ((*s).*(lm))(args...);
|
||||||
|
else lm(args...);
|
||||||
|
} })
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
template <%> auto %<%>::operator()(%) const
|
template <%> auto %<%>::operator()(%) const
|
||||||
|
@ -2652,16 +2658,22 @@ struct WINRT_IMPL_EMPTY_BASES produce_dispatch_to_overridable<T, D, %>
|
||||||
%([o = std::move(object), method](auto&&... args) { return ((*o).*(method))(args...); })
|
%([o = std::move(object), method](auto&&... args) { return ((*o).*(method))(args...); })
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
template <typename O, typename M> %::%(weak_ref<O>&& object, M method) :
|
template <typename O, typename LM> %::%(weak_ref<O>&& object, LM&& lambda_or_method) :
|
||||||
%([o = std::move(object), method](auto&&... args) { if (auto s = o.get()) { ((*s).*(method))(args...); } })
|
%([o = std::move(object), lm = std::forward<LM>(lambda_or_method)](auto&&... args) { if (auto s = o.get()) {
|
||||||
|
if constexpr (std::is_member_function_pointer_v<LM>) ((*s).*(lm))(args...);
|
||||||
|
else lm(args...);
|
||||||
|
} })
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
template <typename O, typename M> %::%(std::shared_ptr<O>&& object, M method) :
|
template <typename O, typename M> %::%(std::shared_ptr<O>&& object, M method) :
|
||||||
%([o = std::move(object), method](auto&&... args) { return ((*o).*(method))(args...); })
|
%([o = std::move(object), method](auto&&... args) { return ((*o).*(method))(args...); })
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
template <typename O, typename M> %::%(std::weak_ptr<O>&& object, M method) :
|
template <typename O, typename LM> %::%(std::weak_ptr<O>&& object, LM&& lambda_or_method) :
|
||||||
%([o = std::move(object), method](auto&&... args) { if (auto s = o.lock()) { ((*s).*(method))(args...); } })
|
%([o = std::move(object), lm = std::forward<LM>(lambda_or_method)](auto&&... args) { if (auto s = o.lock()) {
|
||||||
|
if constexpr (std::is_member_function_pointer_v<LM>) ((*s).*(lm))(args...);
|
||||||
|
else lm(args...);
|
||||||
|
} })
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
inline auto %::operator()(%) const
|
inline auto %::operator()(%) const
|
||||||
|
|
|
@ -180,8 +180,11 @@ namespace winrt::impl
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename O, typename M> delegate_base(winrt::weak_ref<O>&& object, M method) :
|
template <typename O, typename LM> delegate_base(winrt::weak_ref<O>&& object, LM&& lambda_or_method) :
|
||||||
delegate_base([o = std::move(object), method](auto&& ... args) { if (auto s = o.get()) { ((*s).*(method))(args...); } })
|
delegate_base([o = std::move(object), lm = std::forward<LM>(lambda_or_method)](auto&&... args) { if (auto s = o.get()) {
|
||||||
|
if constexpr (std::is_member_function_pointer_v<LM>) ((*s).*(lm))(args...);
|
||||||
|
else lm(args...);
|
||||||
|
}})
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,8 +193,11 @@ namespace winrt::impl
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename O, typename M> delegate_base(std::weak_ptr<O>&& object, M method) :
|
template <typename O, typename LM> delegate_base(std::weak_ptr<O>&& object, LM&& lambda_or_method) :
|
||||||
delegate_base([o = std::move(object), method](auto&& ... args) { if (auto s = o.lock()) { ((*s).*(method))(args...); } })
|
delegate_base([o = std::move(object), lm = std::forward<LM>(lambda_or_method)](auto&&... args) { if (auto s = o.lock()) {
|
||||||
|
if constexpr (std::is_member_function_pointer_v<LM>) ((*s).*(lm))(args...);
|
||||||
|
else lm(args...);
|
||||||
|
}})
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,55 +8,85 @@ using namespace Windows::Foundation::Collections;
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
bool destroyed{};
|
struct Counters
|
||||||
int strong_count{};
|
{
|
||||||
int weak_count{};
|
bool destroyed{};
|
||||||
|
int strong_count{};
|
||||||
|
int weak_count{};
|
||||||
|
int weak_lambda_count{};
|
||||||
|
|
||||||
|
bool is_count(int value)
|
||||||
|
{
|
||||||
|
return strong_count == value && weak_count == value && weak_lambda_count == value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
template <typename Sender, typename Args>
|
template <typename Sender, typename Args>
|
||||||
struct Object : implements<Object<Sender, Args>, IInspectable>
|
struct Object : implements<Object<Sender, Args>, IInspectable>
|
||||||
{
|
{
|
||||||
|
std::shared_ptr<Counters> m_counters;
|
||||||
|
|
||||||
|
Object(std::shared_ptr<Counters> const& counters) : m_counters(counters) {}
|
||||||
|
|
||||||
|
static auto make(std::shared_ptr<Counters> const& counters)
|
||||||
|
{
|
||||||
|
return make_self<Object>(counters);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto strong() { return this->get_strong(); }
|
||||||
|
auto weak() { return this->get_weak(); }
|
||||||
|
|
||||||
~Object()
|
~Object()
|
||||||
{
|
{
|
||||||
destroyed = true;
|
m_counters->destroyed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StrongHandler(Sender const&, Args const&)
|
void StrongHandler(Sender const&, Args const&)
|
||||||
{
|
{
|
||||||
++strong_count;
|
REQUIRE(!m_counters->destroyed);
|
||||||
|
++m_counters->strong_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WeakHandler(Sender const&, Args const&)
|
void WeakHandler(Sender const&, Args const&)
|
||||||
{
|
{
|
||||||
++weak_count;
|
REQUIRE(!m_counters->destroyed);
|
||||||
|
++m_counters->weak_count;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Sender, typename Args>
|
template <typename Sender, typename Args>
|
||||||
struct ObjectStd : std::enable_shared_from_this<ObjectStd<Sender, Args>>
|
struct ObjectStd : std::enable_shared_from_this<ObjectStd<Sender, Args>>
|
||||||
{
|
{
|
||||||
|
std::shared_ptr<Counters> m_counters;
|
||||||
|
|
||||||
|
ObjectStd(std::shared_ptr<Counters> const& counters) : m_counters(counters) {}
|
||||||
|
|
||||||
|
static auto make(std::shared_ptr<Counters> const& counters)
|
||||||
|
{
|
||||||
|
return std::make_shared<ObjectStd>(counters);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto strong() { return this->shared_from_this(); }
|
||||||
|
auto weak() { return this->weak_from_this(); }
|
||||||
|
|
||||||
~ObjectStd()
|
~ObjectStd()
|
||||||
{
|
{
|
||||||
destroyed = true;
|
m_counters->destroyed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StrongHandler(Sender const&, Args const&)
|
void StrongHandler(Sender const&, Args const&)
|
||||||
{
|
{
|
||||||
++strong_count;
|
++m_counters->strong_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WeakHandler(Sender const&, Args const&)
|
void WeakHandler(Sender const&, Args const&)
|
||||||
{
|
{
|
||||||
++weak_count;
|
++m_counters->weak_count;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ReturnObject : implements<ReturnObject, IInspectable>
|
struct ReturnObject : implements<ReturnObject, IInspectable>
|
||||||
{
|
{
|
||||||
~ReturnObject()
|
|
||||||
{
|
|
||||||
destroyed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Handler(int a, int b)
|
int Handler(int a, int b)
|
||||||
{
|
{
|
||||||
return a + b;
|
return a + b;
|
||||||
|
@ -65,91 +95,63 @@ namespace
|
||||||
|
|
||||||
struct ReturnObjectStd : std::enable_shared_from_this<ReturnObjectStd>
|
struct ReturnObjectStd : std::enable_shared_from_this<ReturnObjectStd>
|
||||||
{
|
{
|
||||||
~ReturnObjectStd()
|
|
||||||
{
|
|
||||||
destroyed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Handler(int a, int b)
|
int Handler(int a, int b)
|
||||||
{
|
{
|
||||||
return a + b;
|
return a + b;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Delegate, typename Sender, typename Args>
|
template <typename Recipient, typename Delegate>
|
||||||
void test_delegate_winrt()
|
void test_delegate_pattern()
|
||||||
{
|
{
|
||||||
auto object = make_self<Object<Sender, Args>>();
|
auto counters = std::make_shared<Counters>();
|
||||||
|
auto object = Recipient::make(counters);
|
||||||
|
|
||||||
Delegate strong{ object->get_strong(), &Object<Sender, Args>::StrongHandler };
|
Delegate strong{ object->strong(), &Recipient::StrongHandler};
|
||||||
Delegate weak{ object->get_weak(), &Object<Sender, Args>::WeakHandler };
|
Delegate weak{ object->weak(), &Recipient::WeakHandler };
|
||||||
|
Delegate weak_lambda{ object->weak(),[counters](auto&&, auto&&) {
|
||||||
|
REQUIRE(!counters->destroyed);
|
||||||
|
++counters->weak_lambda_count;
|
||||||
|
} };
|
||||||
|
|
||||||
destroyed = false;
|
// All handlers are active at this point
|
||||||
strong_count = 0;
|
|
||||||
weak_count = 0;
|
|
||||||
|
|
||||||
// Both weak and strong handlers
|
|
||||||
strong({}, {});
|
strong({}, {});
|
||||||
weak({}, {});
|
weak({}, {});
|
||||||
REQUIRE(strong_count == 1);
|
weak_lambda({}, {});
|
||||||
REQUIRE(weak_count == 1);
|
REQUIRE(counters->is_count(1));
|
||||||
|
|
||||||
// Local 'object' strong reference is released
|
// Local 'object' strong reference is released
|
||||||
object = nullptr;
|
object = nullptr;
|
||||||
|
|
||||||
// Still both since strong handler keeps object alive
|
// Still invoked since strong handler keeps object alive
|
||||||
strong({}, {});
|
strong({}, {});
|
||||||
weak({}, {});
|
weak({}, {});
|
||||||
REQUIRE(strong_count == 2);
|
weak_lambda({}, {});
|
||||||
REQUIRE(weak_count == 2);
|
REQUIRE(counters->is_count(2));
|
||||||
|
|
||||||
// ~Object is called since the strong delegate is destroyed
|
// ~Recipient is called since the strong delegate is destroyed
|
||||||
REQUIRE(!destroyed);
|
REQUIRE(!counters->destroyed);
|
||||||
strong = nullptr;
|
strong = nullptr;
|
||||||
REQUIRE(destroyed);
|
REQUIRE(counters->destroyed);
|
||||||
|
|
||||||
// Weak delegate remains but no longer fires
|
// Weak delegate remains but no longer fires
|
||||||
REQUIRE(weak_count == 2);
|
// Strong delegate shouldn't fire either
|
||||||
|
REQUIRE(counters->is_count(2));
|
||||||
weak({}, {});
|
weak({}, {});
|
||||||
REQUIRE(weak_count == 2);
|
weak_lambda({}, {});
|
||||||
|
REQUIRE(counters->is_count(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Delegate, typename Sender, typename Args>
|
||||||
|
void test_delegate_winrt()
|
||||||
|
{
|
||||||
|
test_delegate_pattern<Object<Sender, Args>, Delegate>();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Delegate, typename Sender, typename Args>
|
template <typename Delegate, typename Sender, typename Args>
|
||||||
void test_delegate_std()
|
void test_delegate_std()
|
||||||
{
|
{
|
||||||
auto object = std::make_shared<ObjectStd<Sender, Args>>();
|
test_delegate_pattern<ObjectStd<Sender, Args>, Delegate>();
|
||||||
|
|
||||||
Delegate strong{ object->shared_from_this(), &ObjectStd<Sender, Args>::StrongHandler };
|
|
||||||
Delegate weak{ object->weak_from_this(), &ObjectStd<Sender, Args>::WeakHandler };
|
|
||||||
|
|
||||||
destroyed = false;
|
|
||||||
strong_count = 0;
|
|
||||||
weak_count = 0;
|
|
||||||
|
|
||||||
// Both weak and strong handlers
|
|
||||||
strong({}, {});
|
|
||||||
weak({}, {});
|
|
||||||
REQUIRE(strong_count == 1);
|
|
||||||
REQUIRE(weak_count == 1);
|
|
||||||
|
|
||||||
// Local 'object' strong reference is released
|
|
||||||
object = nullptr;
|
|
||||||
|
|
||||||
// Still both since strong handler keeps object alive
|
|
||||||
strong({}, {});
|
|
||||||
weak({}, {});
|
|
||||||
REQUIRE(strong_count == 2);
|
|
||||||
REQUIRE(weak_count == 2);
|
|
||||||
|
|
||||||
// ~Object is called since the strong delegate is destroyed
|
|
||||||
REQUIRE(!destroyed);
|
|
||||||
strong = nullptr;
|
|
||||||
REQUIRE(destroyed);
|
|
||||||
|
|
||||||
// Weak delegate remains but no longer fires
|
|
||||||
REQUIRE(weak_count == 2);
|
|
||||||
weak({}, {});
|
|
||||||
REQUIRE(weak_count == 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Delegate, typename Sender, typename Args>
|
template <typename Delegate, typename Sender, typename Args>
|
||||||
|
|
Загрузка…
Ссылка в новой задаче