I want to have a class that receives a mutex type as template parameter to be (conditionally) thread-safe. If the mutex type is equal to NoMutex(my mutex empty implementation), I don't want to add this mutex member to my class so I can save space. I want to do something like this:
class NoMutex{
// Same interface as std::mutex but empty implementation
// so optimization can remove unnecessary function calls
};
template <class Mutex>
class MyClass {
public:
// Actually I want to hide it somehow. I dont want it to occupy any space if Mutex == NoMutex
some_template_trick<std::is_same_v<Mutex, NoMutex, void, Mutex> mutex;
void someFunction(){
std::unique_lock<Mutex> l(getMutex()); // I want to use std::unique_lock here
// do some concurrent stuff
}
Mutex& getMutex(){
if constexpr(std::is_same_v<Mutex, NoMutex>){
//ok... return reference to temporary is wrong, but i'm hopping
//that gcc will realize there is nothing to be done once all implementations
//are empty.
return NoMutex();
}
return mutex;
}
};
I don't want to specialize the class nor create lock() unlock() methods, I want them to be managed by std::unique_lock();Is it possible?
Thanks in advance!
Empty data members can be optimized away using empty base-class optimization (EBO). That is, although a stateless class has non-zero size (at least 1 byte), it doesn't consume additional memory if used as a base class:
template <typename Mutex>
struct CompressedMutex
{
Mutex mutex;
Mutex& getMutex() { return mutex; }
};
template <>
struct CompressedMutex<NoMutex> : NoMutex
{
NoMutex& getMutex() { return *this; }
};
template <typename Mutex>
class MyClass : CompressedMutex<Mutex>
{
public:
void someFunction()
{
std::unique_lock<Mutex> l(this->getMutex());
}
};
Note that this->
or CompressedMutex<Mutex>::
is required when accessing getMutex()
which is a dependent name.
Usually, however, one would want to lock a mutex also in const
-qualified member functions. For that, the mutable
keyword could be used in the definition of a mutex data member. In the NoMutex
case that would instead require using const_cast<CompressedMutex&>(*this)
, or declaring a static
object that is returned instead:
template <typename Mutex>
struct CompressedMutex
{
mutable Mutex mutex;
Mutex& getMutex() const { return mutex; }
};
template <>
struct CompressedMutex<NoMutex>
{
static NoMutex mutex;
NoMutex& getMutex() const { return mutex; }
};
This technique has been widely used in the standard library so that a stateless allocator (including std::allocator<T>
) does not account for the total size of a container. Such objects are usually stored in a so called compressed pair (eg, boost::compressed_pair
) or its variation, sometimes together with a non-empty data member, so that the interface of an enclosing class does not change:
#include <boost/compressed_pair.hpp>
template <typename Mutex>
class MyClass
{
public:
void someFunction()
{
std::unique_lock<Mutex> l(data.second());
}
private:
boost::compressed_pair<SomeDataMemberType, Mutex> data;
};
You can do this with a struct wrapper holding either one or two things (untested code):
template <class T1, class T2> class Pair {
Pair (const T1 &t1, const T2 &t2) : t1_(t1), t2_(t2) {}
T1 & first () { return t1_; }
T2 & second () { return t2_; }
T1 t1_;
T2 t2_;
};
template <class T1> class Pair<T1, NoMutex> {
Pair (const T1 &t1) : t1_(t1) {}
T1 & first () { return t1_; }
NoMutex second () { return NoMutex(); }
T1 t1_;
};
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.