Having an object and a mutex as 2 different variables is kinda error prone
MyObject myObject;
std::mutex myObjectMutex;
I tend to forget to lock it sometimes.
On Rust, a shared object is required to be inside a mutex:
std::sync::Mutex<MyObject> mySharedObject
So I have to use like this:
mySharedObject.lock().unwrap().objectMethod()
What would be the less error prone way to simulate something like this in C++ so I don't forget to lock it?
I thought of an std::tuple<std::mutex, MyObject>
but it's not very good and I can forget to lock.
One way to do it is have your Mutex<T>
only allow access to the contained T
via a lambda:
template <typename T>
class Mutex {
private:
T value;
std::mutex mutex;
public:
// Fill out some constructors, probably some kind of emplacement constructor too.
// For simplicity of the example, this should be okay:
explicit Mutex(T value)
: value(std::move(value))
{}
template <typename F>
auto locked(F&& fn) const& -> std::invoke_result_t<F&&, T const&> {
// Lock the mutex while invoking the function.
// scoped_lock automatically unlocks at the end of the scope
std::scoped_lock lock(mutex);
return std::invoke(std::forward<F>(fn), value);
}
template <typename F>
auto locked(F&& fn) & -> std::invoke_result_t<F&&, T&> {
std::scoped_lock lock(mutex);
return std::invoke(std::forward<F>(fn), value);
}
// Can be worth repeating for const&& and && as well
};
Usage:
mySharedObject.locked([] (MyObject& obj) { return obj.objectMethod(); });
It's still possible to defeat this by stashing a reference to the obj
inside a .locked(...)
call and using the reference outside of .locked(...)
, but that would almost require deliberately trying to do the wrong thing.
Also, be aware that it being in a lambda can be quite limiting, as regular control flow no longer works (return
doesn't return from the outer scope, continue
/break
don't work, etc.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With