As an alternative to providing custom move operations for your class, you could create a generic wrapper:
template <class T>
class PerObject
{
T data;
public:
PerObject() = default;
PerObject(const PerObject&) {}
PerObject& operator= (const PerObject&) { return *this; }
T& operator* () const { return data; }
T* operator-> () const { return &data; }
};
And use it like this:
struct A {
A(const A &other) = delete;
A& operator=(const A &other) = delete;
A(A&& other) = default;
A& operator=(A &&other) = default;
PerObject<std::mutex> lock;
};
The wrapper's copy (and move) operations are no-ops, so the object containing the wrapper will always contain the one it started with.
Caveat: However, based on how your class uses the mutex, the above could actually be dangerous. If the mutex is used to protect other data within the class, then it should likely be locked while the object is being assigned to, so you will have to provide manual move operations anyway. In such case, the code would likely look something like this:
struct A {
A(A&& other) : lock{}, /* anything else you need to move-construct */
{
// Note: it might even be necessary to lock `other.lock` before moving from it, depending on your class's exact semantics and expected use.
}
A& operator=(A &&other)
{
if (this == &other) return *this; // Otherwise, double-locking would be possible
// If you need to lock only this object:
std::unique_lock<std::mutex> l(lock);
// Alternatively, if you need to lock both objects:
std::scoped_lock l(lock, other.lock);
// Now move data from other to this
return *this;
}
std::mutex lock;
};