C++ 在遵循pimpl设计模式的类中实现移动语义的正确方法是什么?

C++ 在遵循pimpl设计模式的类中实现移动语义的正确方法是什么?,c++,C++,做这件事的标准方法是什么 让我们有一个类a,它有一个数据成员std::unique\u ptr m\u impl 例如,类A的移动赋值操作符的内容是否应该如下所示 m_impl = std::move(other.m_impl); 还是这个 *m_impl = std::move(*other.m_impl); 第一种情况比第二种情况更有效。但这会引发一些问题 移动后,另一个的m_impl数据成员为null ptr。“从对象移动”应该在使用时引发异常,还是仅仅让用户由于取消引用nullptr

做这件事的标准方法是什么

让我们有一个
类a
,它有一个数据成员
std::unique\u ptr m\u impl

例如,类A的移动赋值操作符的内容是否应该如下所示

m_impl = std::move(other.m_impl);
还是这个

*m_impl = std::move(*other.m_impl);
第一种情况比第二种情况更有效。但这会引发一些问题

移动后,另一个的m_impl数据成员为null ptr。“从对象移动”应该在使用时引发异常,还是仅仅让用户由于取消引用nullptr而运行时出错

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

从对象移动的对象不应再“活动”(有效但未指定)有效但未指定:

  • 允许破坏
  • 允许重新分配
  • 禁止其他用途
第一个选项,即移动成员(即内部的成员)而不是引用,更可取。

从对象移动的对象不应再“活动”(有效但未指定)有效但未指定:

  • 允许破坏
  • 允许重新分配
  • 禁止其他用途

第一种选择,即移动成员(即内部成员),而不是参考成员,更可取。

前者。当您使用
唯一\u ptr
时,可能是因为对象本身首先可能不可复制和/或移动。因此,移动指针对象通常不是一个选项

移动后,另一个的m_impl数据成员为null ptr

这是从
std::unique\u ptr
移动过来的。它需要支持销毁和重新分配,其他一切都是未定义的,不管它做什么(空指针情况下为segfault)

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

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

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

您可以针对对象上的所有操作锁定move操作,并定义moved from对象上操作的语义,以某种方式返回错误,但这听起来很痛苦,而且使用起来非常混乱

线程安全对象实际上应该是例外。您应该致力于在线程之间移交对象,以便在任何给定时间只有一个线程拥有该对象,并且所有共享数据都是不可变的(除了可能的引用计数,这可以通过合理性能的
std::atomic
std::shared\u ptr
来完成,后者构建在它之上)


如果您真的需要线程安全的对象,那么它要么不能移动,要么应该像句柄一样通过
std::shared_ptr
引用它的内部,这是线程安全的。

前者。当您使用
唯一\u ptr
时,可能是因为对象本身首先可能不可复制和/或移动。因此,移动指针对象通常不是一个选项

移动后,另一个的m_impl数据成员为null ptr

这是从
std::unique\u ptr
移动过来的。它需要支持销毁和重新分配,其他一切都是未定义的,不管它做什么(空指针情况下为segfault)

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

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

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

您可以针对对象上的所有操作锁定move操作,并定义moved from对象上操作的语义,以某种方式返回错误,但这听起来很痛苦,而且使用起来非常混乱

线程安全对象实际上应该是例外。您应该致力于在线程之间移交对象,以便在任何给定时间只有一个线程拥有该对象,并且所有共享数据都是不可变的(除了可能的引用计数,这可以通过合理性能的
std::atomic
std::shared\u ptr
来完成,后者构建在它之上)


如果您真的需要线程安全的对象,那么它要么不能移动,要么应该像句柄一样通过
std::shared_ptr
引用它的内部结构,这是线程安全的。

让我们从另一个主题来了解这一点

如何交换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);

但是,作为一种效果,您的类的用户不应该在moved from对象中取消引用
m_impl
。这是move语义在类中使用时引入的内容。由类的用户来防止这种情况的发生,就像如果
v
是从
std::vector
移动过来的,那么代码就不应该这样做一样。

让我们从另一个主题来了解这一点

如何交换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);
但是,作为一种效果,您的类的用户不应该在移动的fr中取消引用
m\u impl