Reputation: 5349
Let's imagine I have a non-default-constructible class like so :
class A {
public:
int k;
A() = delete;
A(int _k): k{_k}{};
A(A const& o) = delete;
A& operator=(A const& o) = delete;
A(A&& o) = default;
A& operator=(A&& o) = default;
};
Then, I have a simple mutex :
class Mutex {
public:
void take();
void give();
};
Now, I have a composite class, and I want to protect every operation on the A
class (and other members), including move-constructing them :
class C {
A a;
A b;
Mutex m;
C() = delete;
C(int _k, int _l) : m{}, a{_k}, b{_l} {}
C(C&& other) : m{} { // PROBLEM HERE : use of deleted constructor
other.m.take(); // <-- this disallows the use of initializer list
a{std::move(other.a)};
b{std::move(other.b)};
other.m.give();
}
};
This throws an error because it tries to default-construct the a
member before entering the constructor body. Is there a way to protect the move-construction of a
with the mutex ?
Upvotes: 1
Views: 224
Reputation: 117422
You could do the locking in a helper function:
class C {
A a;
std::mutex m; // using a standard mutex instead
A A_mover(C&& other) {
std::lock_guard<std::mutex> lock(other.m);
return std::move(other.a); // move into a temporary while locked
}
public:
C() = delete;
C(int _k) : a{_k}, m{} {}
C(C&& other) : a(A_mover(std::move(other))), m{} {}
};
If C
itself is composed of multiple fields, move the mutex out to a wrapper class. The wrapper should ideally keep only one object + a mutex. This uses your Mutex
as it seems the standard std::mutex
isn't available.
class C {
A a;
A b;
public:
C() = delete;
C(int _k, int _l) : a{_k}, b{_l} {}
C(C&& other) = default;
};
class CLocker {
public:
template<typename...Args>
CLocker(Args...args) : c(std::forward<Args>(args)...) {}
CLocker(CLocker&& other) : c(mover(std::move(other))) {}
private:
struct MutexLockGuard {
MutexLockGuard(Mutex& M) : m(M) { m.take(); }
~MutexLockGuard() { m.give(); }
Mutex& m;
};
C mover(CLocker&& other) {
MutexLockGuard lock(m);
return std::move(other.c); // move into a temporary while locked
}
C c;
Mutex m;
};
int main() {
CLocker cl1(10, 20);
CLocker cl2(std::move(cl1));
}
Finally an option without a wrapper suggested by @Jarod42:
class MutexLockGuard {
public:
MutexLockGuard(Mutex& M) : m(M) { m.take(); }
~MutexLockGuard() { m.give(); }
private:
Mutex& m;
};
class C {
public:
C() = delete;
C(int _k, int _l) : a{_k}, b{_l}, m{} {}
C(C&& other) : C(MutexLockGuard(other.m), std::move(other)) {}
//^ delegate to protected constructor
protected:
C(MutexLockGuard, C&& other) : // do the moves while the lock is held
a{std::move(other.a)},
b{std::move(other.b)},
m{}
{}
private:
A a;
A b;
Mutex m;
};
Upvotes: 3