移动具有非默认可构造保护的构造函数 class

Move constructor with protection for non-default-constructible class

让我们假设我有一个非默认构造的 class 像这样:

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

然后,我有一个简单的互斥量:

class Mutex {
    public:
        void take();
        void give();
};

现在,我有一个组合 class,我想保护 A class(和其他成员)上的每个操作,包括移动构造它们:

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

这会引发错误,因为它会在进入构造函数主体之前尝试默认构造 a 成员。 有没有办法用互斥锁保护 a 的移动构造?

您可以在辅助函数中进行锁定:

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

如果 C 本身由多个字段组成,请将互斥量移出包装器 class。理想情况下,包装器应该只保留一个对象 + 一个互斥体。这使用了您的 Mutex,因为标准 std::mutex 似乎不可用。

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

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