I've been finding myself wanting the following pattern recently:
void perform_async_operation(std::function<void(int)>);
class some_class {
public:
void foo() {
perform_async_operation([this](int result) {
data_ = result;
});
}
private:
int data_;
};
Now, the issue here is that the lifetime of some_class
if fully disconnected from when the callback will be invoked, and I do not want to move to shared ownership to put a shared_ptr
in the lambda (like how the boost::ASIO examples handle that situation).
I do, however, have a saving grace: I know for certain that the callback will be invoked in the same thread that manages the lifetime of some_class
.
In order to solve this, I've created a bastardized version of weak_ptr
(with a matching equivalent of enabled_shared_from_this
) that does not require the object to be managed by a shared_ptr
in the first place.
#ifndef SLT_WEAK_REF_INCLUDED_H
#define SLT_WEAK_REF_INCLUDED_H
#include <memory>
template <typename T>
class weak_ref {
public:
weak_ref(std::shared_ptr<T*> weak_data) : weak_data_(std::move(weak_data)) {}
operator bool() const { return *weak_data_ != nullptr; }
T* operator->() const { return *weak_data_; }
private:
// Can be safely assumed to always be set.
std::shared_ptr<T*> weak_data_;
};
template <typename CRTP>
class enable_weak_ref {
public:
enable_weak_ref() = default;
enable_weak_ref(enable_weak_ref&& rhs)
: weak_data_(std::move(rhs.weak_data_)) {
if (weak_data_) {
*weak_data_ = this;
}
}
enable_weak_ref& operator=(enable_weak_ref&& rhs) {
weak_data_ = std::move(rhs.weak_data_);
if (weak_data_) {
*weak_data_ = static_cast<CRTP*>(this);
}
}
// explicitely do nothing
enable_weak_ref(enable_weak_ref const& rhs) {}
enable_weak_ref& operator=(enable_weak_ref const&) {}
~enable_weak_ref() {
if (weak_data_) {
*weak_data_ = nullptr;
}
}
weak_ref<CRTP> get_weak_ref() {
if (!weak_data_) {
weak_data_ = std::make_shared<CRTP*>(static_cast<CRTP*>(this));
}
return weak_ref<CRTP>(weak_data_);
}
private:
// TODO: perhaps switch to a simple manually ref-counted type here.
// instead of the overkill shared_ptr<>
std::shared_ptr<CRTP*> weak_data_;
};
#endif
sample usage:
#include "weak_ref.h"
#include <queue>
#include <iostream>
#include <functional>
std::queue<std::function<void(int)>> queue_;
void queue_delayed(std::function<void(int)> cb) {
queue_.emplace(std::move(cb));
}
class my_class : public enable_weak_ref<my_class> {
public:
my_class() {
queue_delayed([ref = get_weak_ref()](int v){
if(ref) {
std::cout << "passed " << v << std::endl;
ref->value = v;
}
else {
std::cout << "saved " << v << std::endl;
}
});
}
int value = 0;
};
int main() {
my_class a;
queue_.front()(12);
queue_.pop();
{
my_class b;
}
queue_.front()(5);
queue_.pop();
return 0;
}
Notes:
- Moving an object in a lambda is a C++14 feature, without which, a lot of back-and-forth ref-counting happens.
- As mentioned, this is intentionally not thread-safe. Thread safety can be added if anyone needs it eventually.
Issues:
- Is there a nicer way to tackle this I'm overlooking here?
weak_ref
is very weak name, I'd gladly welcome better suggestions.- As always, comments on general code quality are most desired.