这样做的标准方法是什么?
我们的class A
有一个数据成员std::unique_ptr<Impl> m_impl
。
例如,A类移动赋值运算符的内容应该是这样的吗?
m_impl = std::move(other.m_impl);
还是这个?
*m_impl = std::move(*other.m_impl);
第一种情况比第二种情况更有效。但它会引发一些问题。
移动后,其他的m_impl数据成员为nullptr。移动对象在被使用时是否会抛出异常,或者由于解除引用nullptr而让用户遇到运行时错误?
对于线程安全类,这也意味着需要同步所有m_impl用法。我不确定调用std::unique_ptr
的移动构造函数/赋值运算符是否是线程安全的。
答案 0 :(得分:2)
移动物体不应该是&#34;活着&#34;再次(有效但未指明)有效但未指明:
第一个选项,即移动成员(即内部的成员)而不是引用,更可取。
答案 1 :(得分:2)
前者。当您使用unique_ptr
时,您可能正在这样做,因为对象本身可能首先不可复制和/或移动。因此,移动指针通常不是一种选择。
移动后,其他的m_impl数据成员为nullptr。
这是正确的移动 - 来自std::unique_ptr
。它需要支持破坏和重新分配,其他所有内容都是未定义的,并且无关紧要(在空指针的情况下发生了段错误)。
对于线程安全类,这也意味着需要同步所有m_impl用法。
除非明确说明,否则标准库不是线程安全的,只能是可重入的。如果你的对象需要是线程安全的,你必须自己确保它。
如果线程移动了一个不是唯一所有者的对象,则会导致其他线程出现问题,因为他们仍然会尝试在旧位置访问它。
您可以针对对象上的所有操作锁定移动操作,并定义移动对象上的操作的语义以某种方式返回错误,但这听起来很痛苦并且使用起来非常混乱。
线程安全对象应该是异常。您应该在线程之间处理对象,以便在任何给定时间只有一个拥有该对象,并使所有共享数据不可变(除了可能的引用计数,可以通过合理的性能std::atomic
或{{1}来完成},它建立在它之上)。
如果你真的需要一个线程安全的对象,它应该不是可移动的,或者应该像std::shared_ptr
那样引用它的内部的句柄, 是线程安全的。< / p>
答案 2 :(得分: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[0]
v
移动来自std::vector
等等。