代码之家  ›  专栏  ›  技术社区  ›  Magix

为非默认可构造类设置列表初始化器

  •  0
  • Magix  · 技术社区  · 4 年前

    让我们想象一下,我有一个非默认的可构造类,如下所示:

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

    现在,我有一个复合类,我想保护 A 类(和其他成员),包括移动构建它们:

    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 在进入构造函数体之前。 有没有办法保护移动建筑 使用互斥体?

    0 回复  |  直到 4 年前
        1
  •  3
  •   Ted Lyngmo    4 年前

    您可以在辅助函数中执行锁定操作:

    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 它本身由多个字段组成,将互斥体移到包装器类中。理想情况下,包装器应该只保留一个对象+一个互斥体。这使用你的 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;       
    };