【问题标题】:What is the right way to implement move semantics in a class that follows the pimpl design pattern?在遵循 pimpl 设计模式的类中实现移动语义的正确方法是什么?
【发布时间】:2021-05-24 13:07:20
【问题描述】:

标准的做法是什么?

让我们有一个class A,它有一个数据成员std::unique_ptr<Impl> m_impl

例如,类 A 的移动赋值运算符的内容应该是这样的吗?

m_impl = std::move(other.m_impl);

还是这个?

*m_impl = std::move(*other.m_impl);

第一种情况比第二种更有效。但这会引发一些问题。

移动后,other的m_impl数据成员为nullptr。被移动的对象应该在使用时抛出异常,还是让用户由于取消引用 nullptr 而遇到运行时错误?

对于线程安全的类,这也意味着所有 m_impl 的使用都需要同步。我不确定调用std::unique_ptr 的移动构造函数/赋值运算符是否是线程安全的。

【问题讨论】:

    标签: c++


    【解决方案1】:

    前者。当您使用unique_ptr 时,您可能正在这样做,因为对象本身可能一开始就不可复制和/或可移动。所以移动指针通常不是一种选择。

    移动后other的m_impl数据成员为nullptr。

    std::unique_ptr 移出是正确的。它需要支持销毁和重新分配,其他所有内容都是未定义的,并且与它的作用无关(在空指针的情况下会出现段错误)。

    对于线程安全的类,这也意味着所有 m_impl 的使用都需要同步。

    除非明确说明,否则标准库不是线程安全的,只能重入。如果你的对象需要是线程安全的,你必须自己确保它。

    也就是说,如果一个线程移动了一个它不是其唯一所有者的对象,则会给其他线程带来问题,因为它们仍会尝试在旧位置访问它。

    您可以针对对象上的所有操作锁定移动操作,并定义移动对象上操作的语义以以某种方式返回错误,但这听起来很痛苦并且使用起来严重混乱。

    线程安全的对象应该是例外。您的目标应该是在线程之间传递对象,以便在任何给定时间只有一个人拥有该对象,并使所有共享数据不可变(可能的引用计数除外,这可以通过合理的性能std::atomicstd::shared_ptr 来完成,即建立在它之上)。

    如果你真的需要一个线程安全的对象,它应该是不可移动的,或者应该表现得像 std::shared_ptr 引用它的内部的句柄,这线程安全的。

    【讨论】:

    • 你会说如果 A 需要是线程安全的,那么 A::Impl 是线程安全的就足够了吗?同时使用/移动多个线程应该被视为无效使用吗?我最初考虑让指针移动是线程安全的,并在它为 nullptr 时抛出异常,但也许这太多了。
    • @NFRCR:我要么根本不让应该从多个线程访问的任何东西都可移动,要么我会让它表现得像一个句柄,即用作 shared实施(通过shared_ptr)。但是考虑更多它也是使对象可移动的一种选择,将其锁定在移动中(您自己,因为您必须排除对它的所有操作)并在对象被移开时报告错误(对移开对象的操作通常是未定义的,但你可以为你正在实现的类定义它们)。
    • @NFRCR:线程安全的对象应该很少见。除了您可以摆脱原子的少数情况之外,线程同步是棘手的​​、昂贵的并且限制了可伸缩性。您的目标应该是在任何给定时间在一个唯一所有者的线程之间移交对象,或者将共享数据设为只读。
    【解决方案2】:

    让我们用不同的主题来看待这个问题。

    如何交换 pimpl 类对象?

    void swap(T& x, T& y) {
       std::swap(x.m_impl, y.m_impl);
    }
    

    对吗?

    它只在m_impl 指针周围移动。因此,使用移动语义,您也应该只移动 m_impl 指针!

    m_impl = std::move(other.m_impl);
    

    然而,作为一个效果,你的类的用户不应该在一个移动的对象中取消引用m_impl。当你在你的类中使用它时,这是移动语义引入的东西。您的班级的用户可以防止这种情况发生,就像如果v 是从std::vector 移出的v[0] 等,您的代码不应执行v[0]

    【讨论】:

      【解决方案3】:

      一个移出的对象是不再应该是“活着的”(有效但未指定)有效但未指定:

      • 允许销毁
      • 允许重新分配
      • 禁止其他用途

      第一个选项,即移动 member(即内脏上的那些)而不是 referencee,更可取。

      【讨论】:

        猜你喜欢
        • 2019-02-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-12-22
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多