Skip to content

Instantly share code, notes, and snippets.

@nixiz
Created April 19, 2021 19:06
Show Gist options
  • Save nixiz/5bc33bbc7d439e2c0e2a293a4bd55664 to your computer and use it in GitHub Desktop.
Save nixiz/5bc33bbc7d439e2c0e2a293a4bd55664 to your computer and use it in GitHub Desktop.
Thread safe accessor encapsulation implementation in C++
#include <iostream>
#include <mutex>
#include <assert.h>
/*
From the book "Modern C++ Design: Generic Programming and Design Patterns Applied" Andrei Alexandrescu
There are interesting applications of smart pointer layering, mainly because of the mechanics of
operator->. When you apply operator-> to a type that's not a built-in pointer, the compiler does an
interesting thing. After looking up and applying the user-defined operator-> to that type, it applies
operator-> again to the result. The compiler keeps doing this recursively until it reaches a pointer to a
built-in type, and only then proceeds with member access. It follows that a smart pointer's operator->
does not have to return a pointer. It can return an object that in turn implements operator->, without
changing the use syntax.
This leads to a very interesting idiom: pre-and postfunction calls (Stroustrup 2000). If you return an object
of type PointerType by value from operator->, the sequence of execution is as follows:
1. Constructor of PointerType
2. PointerType::operator-> called; likely returns a pointer to an object of type
PointeeType
3. Member access for PointeeType?likely a function call
4. Destructor of PointerType
In a nutshell, you have a nifty way of implementing locked function calls. This idiom has broad uses with
multithreading and locked resource access. You can have PointerType's constructor lock the resource,
and then you can access the resource; finally, Pointer Type's destructor unlocks the resource.
*/
template <typename T>
struct thread_safe_mediator {
thread_safe_mediator(const T* p) : ptr(const_cast<T*>(p))
{
//printf("\nthread_safe_mediator::thread_safe_mediator()");
if (ptr != 0)
ptr->lock();
}
~thread_safe_mediator(void)
{
//printf("\nthread_safe_mediator::~thread_safe_mediator()");
if (ptr != 0)
ptr->unlock();
}
operator T* ()
{
return ptr;
}
T* operator->()
{
return ptr;
}
private:
thread_safe_mediator() = delete;
// cannot delete copy ctor because of thread safety policy
// classes are returning copy of this class
//thread_safe_mediator(const thread_safe_mediator&) = delete;
thread_safe_mediator& operator = (const thread_safe_mediator&) = delete;
T* ptr;
};
template <typename T>
class thread_safe
{
class lock_helper_t : public T {
public:
template<typename ...Args>
lock_helper_t(Args&& ...args)
: T{ std::forward<Args>(args)... }
, locked(false)
{
//printf("\nLockerHelper_T::lock_helper_t()");
}
~lock_helper_t() {
//printf("\nLockerHelper_T::~lock_helper_t()");
assert(!locked);
}
void lock() const {
std::cout << "lock_helper_t::lock()\n";
guard.lock(); locked = true;
}
void unlock() const {
std::cout << "lock_helper_t::unlock()\n";
assert(locked);
guard.unlock(); locked = false;
}
private:
mutable std::mutex guard;
mutable bool locked;
};
public:
template<typename ...Args>
thread_safe(Args&& ...args)
: ptr{ new lock_helper_t{ std::forward<Args>(args)... } }
{ }
//typedef thread_safe_mediator<lock_helper_t> AccessType;
auto operator->() {
return thread_safe_mediator<lock_helper_t>(ptr);
}
auto operator->() const {
return thread_safe_mediator<lock_helper_t>(ptr);
}
private:
lock_helper_t* ptr;
};
class DummyClass
{
int val;
public:
DummyClass(int x)
: val{x} { }
void foo(int x) {
printf("\nDummyClass::foo()");
val = x;
}
int bar() {
printf("\nDummyClass::bar()");
return val;
}
};
int main()
{
thread_safe<DummyClass> tsafe{ 5 };
tsafe->foo(12);
auto x = tsafe->bar();
return x;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment