template<typename T>
class mostly_harmless::utils::Proxy< T >
Helper class for managing lifetimes of captured references in a lambda.
Take a scenario where you have a timer thread, which relies on some member variable being valid - for example,
class Something final {
public:
Something() {
auto timerCallback = [this]() -> void {
++m_callCount;
};
m_timer.action = std::move(timerCallback);
m_timer.run(100);
}
private:
std::atomic<int> m_callCount{ 0 };
};
Definition mostlyharmless_Timer.h:10
Now, lets say between calls to our timer action, our instance of Something
gets destroyed - but the timer callback is still running. In this case, we have a segfault, as this == nullptr
. This comes up fairly often with timers, and is a massive PITA.
The use case for Proxy then, is to be able to reliably check if the captured variables are still valid. This works by constructing a shared_ptr to some "Proxy" class with a pointer to the data you want to check, and ensuring its lifecycle matches that of the wrapped pointer. When your wrapped object is destroyed, make sure to call null()
on the proxy, to invalidate its pointer member. You can then capture the shared_ptr via copy to your timer callback, which will ensure that while the generated closure for the lambda is alive, so is our proxy. Finally, in your callback, you can simply call theProxyInstance.isValid()
to check whether the wrapped pointer is still valid, and bail if it isn't. Putting that together for our earlier example then:
class Something final {
public:
Something() {
auto proxyCopy = m_proxy;
auto timerCallback = [this, proxyCopy]() -> void {
if(!proxyCopy->isValid()) {
return;
}
++m_callCount;
};
m_timer.action = std::move(timerCallback);
m_timer.run(100);
}
~Something() {
m_proxy->null();
}
private:
std::shared_ptr<mostly_harmless::utils::Proxy<Something>> m_proxy{ nullptr };
std::atomic<int> m_callCount{ 0 };
mostly_harmless::utils::Timer m_timer;
};
static std::shared_ptr< Proxy< T > > create(T *toWrap) noexcept
Definition mostlyharmless_Proxy.h:86