Created
January 27, 2020 19:03
-
-
Save foxcpp/858d1fd5358a43968471006f1b0d2ed3 to your computer and use it in GitHub Desktop.
std::shared_mutex + T value wrapper
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
#pragma once | |
#include <memory> | |
#include <shared_mutex> | |
/* | |
* std::shared_mutex + T value wrapper written out of boredom. | |
* Needs C++17 support to compile. | |
* | |
* It phohibits direct manipulation of T value without locking the mutex in | |
* exclusive way (for non-const operations) or shared way (for const | |
* operations). It is meant to enforce thread-safety invariants in "elegant" | |
* automatic way. | |
* | |
* Here is a couple of usage examples: | |
* | |
* ``` | |
* // 🦀🦀🦀🦀🦀🦀 | |
* locked<int> t(1); | |
* | |
* // Can't access t value directly. | |
* | |
* { | |
* // Have to "acquire" it, it locks the mutex. | |
* auto v = t.const_acquire(); | |
* | |
* // Can do so multiple times. | |
* auto v2 = t.const_acquire(); | |
* | |
* // But these references are read-only. | |
* // *v = 2; | |
* // *v2 = 4; | |
* | |
* std::cout << "Before writes: " << *v << ' ' << *v2 << std::endl; | |
* | |
* // v falls out of scope, unlocking the mutex. | |
* } | |
* | |
* { | |
* auto v = t.acquire(); | |
* | |
* *v = 5; | |
* } | |
* | |
* // "Temporary" acquire. Lock mutex, write variable, unlock. | |
* (*t.acquire())++; | |
* | |
* // "Temporary" acquire. Lock mutex, read variable, unlock. | |
* std::cout << *t.const_acquire() << '\n'; | |
* | |
* // Can't access t value directly. | |
* ``` | |
*/ | |
template<typename T> | |
class locked { | |
public: | |
locked() = default; | |
locked(const T& v) : v(v) {} | |
locked(T&& v) : v(std::move(v)) {} | |
~locked() = default; | |
template<typename U> | |
class observer { | |
public: | |
observer(const locked<T>& l) : v(&l.v), l(&l.l), shared(true) { | |
l.l.lock_shared(); | |
} | |
observer(locked<T>& l) : v(&l.v), l(&l.l), shared(false) { | |
l.l.lock(); | |
} | |
observer() = delete; | |
observer(const observer&) = delete; | |
observer(observer&& rhs) { | |
swap(rhs); | |
rhs.v = nullptr; | |
}; | |
~observer() { | |
this->v = nullptr; | |
if (shared) { | |
this->l->unlock_shared(); | |
} else { | |
this->l->unlock(); | |
} | |
} | |
observer& operator=(observer&& rhs) noexcept { | |
swap(rhs); | |
rhs.v = nullptr; | |
return *this; | |
}; | |
observer& operator=(const observer&) = delete; | |
const U& operator*() const noexcept { | |
return *this->v; | |
} | |
U& operator*() noexcept { | |
return *this->v; | |
} | |
const U* operator->() const noexcept { | |
return this->v; | |
} | |
U* operator->() noexcept { | |
return this->v; | |
} | |
void swap(observer<U>& rhs) noexcept { | |
using std::swap; | |
swap(this->v, rhs.v); | |
swap(this->shared, rhs.shared); | |
swap(this->l, rhs.l); | |
} | |
private: | |
U* v; | |
bool shared; | |
std::shared_mutex* l; | |
}; | |
using acquire_type = observer<T>; | |
using const_acquire_type = observer<const T>; | |
observer<const T> acquire() const { | |
return this->const_acquire(); | |
} | |
observer<const T> const_acquire() const { | |
return observer<const T>(*this); | |
} | |
observer<T> acquire() { | |
return observer<T>(*this); | |
} | |
private: | |
T v; | |
mutable std::shared_mutex l; | |
}; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Nice, this looks really useful! Was wondering if somebody had some code up generalizing this pattern, rather than just sticking a mutex as member and accessing it directly. Perhaps the only thing this is missing is an explicit release function, but it is mostly a nicety / sugar really.