HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Ensure only a single instance of static template class member

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
templateensureinstancemembersingleclassonlystatic

Problem

I am interacting with a C library, whose documentation states that certain functions are not thread safe. Since its functions deal heterogeneously with multiple types, I have written a template wrapper to use it in a homogeneous manner from C++. However, I now need to ensure serialized access to certain functions, serialized across all instantiated template wrappers.

I believe the following approach should work; what I would like to know is, are there any caveats to the following approach, and if so, what are they and how can I get around them?

struct LibMutexPolicy
{
    static std::mutex & getMutexInstance()
    {
        static std::mutex inst;
        return inst;
    }
};

template 
struct A : private LibMutexPolicy
{
    void foo()
    {
        std::lock_guard lock(getMutexInstance());
        // Call non thread-safe library code
    }
};

Solution

I believe the following approach should work; what I would like to know is, are there any caveats to the following approach, and if so, what are they and how can I get around them?

Consider a decorator implementation, with a visitor function for method access:

template
class threadsafe_reference // decorator
{
public:
    using reference_type = V&;
    threadsafe_reference(reference_type v, std::mutex& m): value_(v), mutex_(m) {}

    // synchronized visitor access
    void sync( std::function functor )
    {
        std::lock_guard lock{ mutex_ };
        functor( value_ );
    }

    // you may wish to provide unsynchronized reference access here
private:
    reference_type value_;
    std::mutex& mutex_; 
};

template
threadsafe_reference make_threadsafe(V& value, std::mutex& m)
{
    return threadsafe_reference{ value, m };
}


Client code:

struct A // agnostic to threading
{
    void do_stuff_n_things( int i ) {};
};

A a; // need to pass this on two threads
mutex m;

auto tsa = make_threadsafe(a, m); // proxy accessor


Thread access:

tsa.sync( []( A& self ) { self.do_things_n_stuff(10); } );


The code is incomplete (you'd need a specialization of sync for const access and so on) but it should be enough to give you a picture.

Advantages:

-
this is a non-intrusive decorator that you can use over virtually any type

-
separates concerns nicely and allows you to implement and test them separately (e.g. test A, then test the synchronization code)

Minor disadvantage:

  • the access to A's methods requires lambdas/external functions

Code Snippets

template<typename V>
class threadsafe_reference // decorator
{
public:
    using reference_type = V&;
    threadsafe_reference(reference_type v, std::mutex& m): value_(v), mutex_(m) {}

    // synchronized visitor access
    void sync( std::function<void(value_type&)> functor )
    {
        std::lock_guard<std::mutex> lock{ mutex_ };
        functor( value_ );
    }

    // you may wish to provide unsynchronized reference access here
private:
    reference_type value_;
    std::mutex& mutex_; 
};

template<typename V>
threadsafe_reference<V> make_threadsafe(V& value, std::mutex& m)
{
    return threadsafe_reference<V>{ value, m };
}
struct A // agnostic to threading
{
    void do_stuff_n_things( int i ) {};
};

A a; // need to pass this on two threads
mutex m;

auto tsa = make_threadsafe(a, m); // proxy accessor
tsa.sync( []( A& self ) { self.do_things_n_stuff(10); } );

Context

StackExchange Code Review Q#71956, answer score: 4

Revisions (0)

No revisions yet.