I am working on class for simple sharing values between threads without race-conditions, which can't be a std::atomic (this means, no POD and no trivially-copyable types). The initial intention was to lock a std::function against writing while reading from an other thread.
The class itself is simple; just lock a mutex at every sensitive part of the class, but I am not totally sure if it's correct or not. I think about to lock the mutex in the dtor, but I don't know if it's good style or not.
#pragma once
template <class T>
class LockedValue
{
private:
using Lock = std::scoped_lock<std::mutex>;
public:
LockedValue() = default;
LockedValue(const LockedValue& _other) :
m_Value(_other.load())
{}
LockedValue(LockedValue& _other) :
LockedValue(static_cast<const LockedValue&>(_other))
{}
LockedValue(LockedValue&& _other) :
m_Value(_other._takeValue())
{}
template <class... Args>
LockedValue(Args&&... _args) :
m_Value(std::forward<Args>(_args)...)
{}
LockedValue& operator =(const LockedValue& _other)
{
auto value = _other.load();
store(std::move(value));
return *this;
}
LockedValue& operator =(LockedValue&& _other)
{
if (this != &_other)
store(_other._takeValue());
return *this;
}
template <class T_, typename = std::enable_if_t<std::is_convertible_v<T_, T>>>
LockedValue& operator =(T_&& _value)
{
store(std::forward<T_>(_value));
return *this;
}
T operator *() const
{
return load();
}
template <class T_>
void store(T_&& _value)
{
Lock lock(m_Mutex);
m_Value = std::forward<T_>(_value);
}
T load() const
{
Lock lock(m_Mutex);
return m_Value;
}
private:
mutable std::mutex m_Mutex;
T m_Value;
T&& _takeValue()
{
Lock lock(m_Mutex);
return std::move(m_Value);
}
};
EDIT: Is it necessary to check for self-moving in move assign? I think it should be ok to remove the self check.
EDIT2: To the copy ctor related discussion. Delete the non-const& copy ctor from the class and try to use an object like this.
If you don't have c++17 change the Lock alias to std::lock_guard and change std::is_convertible_v to std::is_convertible::value
I pasted it at this online compiler with the ctor already deactivated click me
// calls the variadic ctor
LockedValue<std::string> s("test");
// this is fine; calls the usual copty ctor
auto u(static_cast<const LockedValue<std::string>&>(s));
/* results in an error because type LockedValue<std::string>&
can not be converted to std::string; this means a second call
for the variadic ctor*/
auto t(s);