Skip to content

Instantly share code, notes, and snippets.

@foxcpp
Created January 27, 2020 19:03
Show Gist options
  • Save foxcpp/858d1fd5358a43968471006f1b0d2ed3 to your computer and use it in GitHub Desktop.
Save foxcpp/858d1fd5358a43968471006f1b0d2ed3 to your computer and use it in GitHub Desktop.
std::shared_mutex + T value wrapper
#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;
};
@matgrioni
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment