Once upon a time I wrote a template with very similar function.For Qt5 and modern C++ standard this would require some adjustments. Note, it was for Qt4, pre-C++11 and QMutex
back then for recursive and non-recursive use was different in initiliazation. Not separate classes.
/** Mutex-protected interface.
@param class Data - type of protected structure
@param QMutex::RecursionMode mode - is mutex recursive?
@example
struct Data {
vector<float> arr;
};
struct StoredData : ILockableData<Data, QMutex::Recursive >
{
};
*/
template <class Data, QMutex::RecursionMode mode = QMutex::NonRecursive>
class ILockableData : public Data
{
// de-coupling of QMutex to permit copy-assignment
struct MutexIsolator
{
QScopedPointer<QMutex> lp;
MutexIsolator(): lp(new QMutex(mode)) {}
MutexIsolator(const MutexIsolator&): lp(new QMutex(mode)) {}
MutexIsolator(MutexIsolator&&): lp(new QMutex(mode)) {}
MutexIsolator& operator=(const MutexIsolator&) {}
MutexIsolator& operator=(MutexIsolator&&) {}
} lck;
QMutex& mutex() const { return *lck.lp.data(); }
public:
ILockableData() {}
ILockableData(const ILockableData& v) {
this->operator=(v);
}
ILockableData(const ILockableData&& v) {
this->operator=(std::move(v));
}
// Mutex-like interface
void lock () { mutex().lock();}
bool tryLock () { return mutex().tryLock();}
bool tryLock ( int timeout ) { return mutex().tryLock(timeout);}
void unlock () { mutex().unlock();}
/** Copy override. Data must be copyable */
ILockableData& operator=(const ILockableData& v) {
if(&v == this) return *this;
QMutexLocker l1(&(this->mutex()));
QMutexLocker l2(&(v.mutex()));
const Data& arg = static_cast<const Data&>(v);
static_cast<Data*>(this)->operator=(arg);
return *this;
}
/** Data must be moveable. */
ILockableData& operator=(ILockableData&& v) {
if(&v == this) return *this;
QMutexLocker l1(&(this->mutex()));
QMutexLocker l2(&(v.mutex()));
Data&& arg = static_cast<Data&&>(v);
static_cast<Data*>(this)->operator=(std::move(arg));
return *this;
}
operator Data() {
QMutexLocker l(&(this->lck));
return static_cast<Data&>(*this);
}
};
This is probably a bare imaginable minimum what is required to use CRTP. But anything derived from this class would have interfaces to operate with Data using QMutex
lock.
Now if you want virtual interface for this, you have to add a Base class from which Data
is derived wich would have pure definitions.
It's not effective. Protecting a single structure is in general not very effective and potentionally dangerous. Workign with massive data, we would need to protect containers, especially if they are associative.We would want to use ordering of operations.
Having a shared mutex state simplifies things. Each ILockableData<Data>
would have one of their own for each Data
type, while still having shared interface and we no longer have to worry about copying.
class Thing
{
public:
Thing() {}
virtual ~Thing() {}
protected: // you can't make this private, for progeny
virtual void load() = 0;
virtual void translate(int amount) = 0;
virtual void clear() = 0;
};
template<class T>
class ThingInterface
{
public:
// do you want these virtual? Need a base class for that
void lockAndLoad() {
QMutexLocker mutexLocker(&static_cast<T*>(this)->mutex());
static_cast<T*>(this)->load();
}
void lockAndTranslate(int amount) {
QMutexLocker mutexLocker(&static_cast<T*>(this)->mutex());
static_cast<T*>(this)->translate(amount);
}
void lockAndClear() {
QMutexLocker mutexLocker(&static_cast<T*>(this)->mutex());
static_cast<T*>(this)->clear();
}
};
template<class T>
class StaticLockable {
static QMutex mut;
protected:
static QMutex& mutex() { return mut; }
// something else?
};
class ConcreteThing : public Thing,
public StaticLockable<ConcreteThing>,
public ThingInterface<ConcreteThing>
{
// this is required to access load();
// Another way is to use Accessor pattern.
friend ThingInterface<ConcreteThing>;
// void lockAndLoad() and co. are public
protected:
void load() override {}
void translate(int amount) override {}
void clear() override {}
};
;
after a class definition a puppet dies somewhere