Last active
February 14, 2024 21:25
-
-
Save saxbophone/0d96c1ea4b5b9320360e61c3726cef38 to your computer and use it in GitHub Desktop.
Seizable --a thread-safe convenience wrapper around std::shared_mutex that allows read-write lock semantics via getters and setters, as well as the ability to "seize" the protected value in order to operate on it more efficiently using reference semantics
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <mutex> | |
#include <shared_mutex> | |
#include <type_traits> | |
// TOOD: reïmplement all methods non-inline so the prototype is nice and readable | |
template <typename T> | |
class Seizable { | |
public: | |
// TODO: change to std::shared_timed_mutex and add additional time-based locking methods | |
using Mutex = std::shared_mutex; | |
class Seized { | |
public: | |
Seized(Mutex& mutex, T& seizee) : _lock{mutex}, _seizee{seizee} {} | |
Seized(const Seized&) = delete; | |
Seized(Seized&& other) | |
: _lock{std::move(other._lock)} | |
, _seizee{other._seizee} | |
{} | |
Seized& operator=(const Seized&) = delete; | |
Seized& operator=(Seized&& other) { | |
_lock = std::move(other._lock); | |
_seizee = other._seizee; | |
return *this; | |
} | |
/* | |
* warning: using this return value is only thread-safe as long as the | |
* Seized object that returned it is still in-scope. | |
*/ | |
operator T&() { return _seizee; } | |
private: | |
std::unique_lock<Mutex> _lock; | |
T& _seizee; | |
}; | |
Seizable() = default; | |
Seizable(const T& value) : _seizee{value} {} | |
Seizable(T&& value) : _seizee{value} {} | |
Seizable(const Seizable&) = delete; | |
Seizable(Seizable&&) = delete; | |
Seizable& operator=(const Seizable&) = delete; | |
Seizable& operator=(Seizable&&) = delete; | |
T get() const { | |
std::shared_lock read_lock(_mutex); | |
return _seizee; | |
} | |
operator T() const { return get(); } | |
void set(const T& value) { _setter(value); } | |
void set(T&& value) { _setter(value); } | |
void operator=(const T& value) { set(value); } | |
void operator=(T&& value) { set(value); } | |
/* | |
* warning: | |
* using the reference returned by Seized's implicit conversion operator is | |
* only thread-safe if the Seized object that returned it is in-scope | |
*/ | |
Seized seize() { return Seized(_mutex, _seizee); } | |
template <typename Callable> | |
decltype(auto) seize_and_do(Callable callback) { | |
auto seizure = seize(); | |
T& seized = seizure; | |
return callback(seized); | |
} | |
private: | |
template <typename Ref> | |
void _setter(Ref value) { | |
std::lock_guard write_lock(_mutex); | |
_seizee = value; | |
} | |
mutable Mutex _mutex; | |
T _seizee; | |
}; | |
int do_something_with_int(int& the_int) { | |
the_int += 443; | |
the_int *= (the_int - 5); | |
int partway = the_int; | |
the_int /= 663; | |
++++the_int; | |
return partway / the_int; | |
} | |
int main() { | |
Seizable<int> foo = 32; | |
int x = foo; | |
int y = 456; | |
foo = y; | |
{ | |
auto seized = foo.seize(); | |
seized += 43; | |
} | |
foo.seize_and_do([](int& seized) { | |
seized *= 2 + seized / 3 + 446 / seized * 585; | |
}); | |
int answer = foo.seize_and_do(do_something_with_int); | |
return answer; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment