简体   繁体   中英

Move constructor with protection for non-default-constructible class

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();
    }
       
};

Try it in Coliru

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?

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;       
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM