Предположим следующий минимальный пример:
#include <iostream>
#include <mutex>
#include <thread>
template<class Parent>
class Synchronized final {
public:
friend Parent;
explicit Synchronized(Parent &parent) : m_parent(parent), m_lock(parent.m_mutex) {}
Synchronized(Synchronized&&) = default;
Parent* operator->() noexcept {
return &m_parent;
}
private:
Synchronized(const Synchronized&) = delete;
Synchronized& operator=(const Synchronized&) = delete;
Synchronized& operator=(Synchronized&&) = delete;
Parent &m_parent;
std::unique_lock<std::mutex> m_lock;
};
template<class T>
class Synchronizeable {
public:
virtual ~Synchronizeable() = default;
Synchronized<T> synchronized() {
return Synchronized<T>(static_cast<T>(*this));
}
private:
friend class Synchronized<T>;
std::mutex m_mutex;
};
class Counter : public Synchronizeable<Counter> {
public:
Counter() {}
int m_val = 0;
};
int main(int argc, char **argv) {
Counter cnt;
std::thread inc([&]() {
Synchronized<Counter> sync(cnt);
for (int i = 0; i < 10; i++) {
sync->m_val++;
std::cout << "INC, new val=" << cnt.m_val << std::endl;
}
});
std::thread dec([&]() {
Synchronized<Counter> sync(cnt);
for (int i = 0; i < 10; i++) {
sync->m_val--;
std::cout << "DEC, new val=" << cnt.m_val << std::endl;
}
});
inc.join();
dec.join();
return 0;
}
Конструкция выглядит следующим образом. Класс реализует интерфейс «Synchronizeable», поэтому он наследует мьютекс и метод класса synchronized (). Это также делает класс «Синхронизированный» другом затронутого класса, поэтому мьютекс может использоваться в его конструкторе.
Экземпляр оболочки «Синхронизированный» может быть построен вокруг любого объекта, который реализует / наследует «Синхронизируемый»"interface.
Каковы проблемы (дизайна) с моими синхронизированными классами? Для меня это выглядит так, как будто эта реализация работала правильно, но также выглядит довольно просто, и я не нашел много решений для достижения объекта типа «Монитор / Синхронизированный» в стиле Java в C ++. Я не совсем уверен, что я делаю достаточно здесь (возможно, что-то упускаю).
(незначительная) проблема стиля заключается в том, что дочерний класс всегда должен наследоваться от интерфейса "Synchronizeable", то есть всегдадолжен передать свое собственное имя класса в качестве параметра шаблона. Я не нашел способа обойти это до сих пор.