假设我有一个管理内存的类,因此需要用户定义的特殊成员函数(想象vector
或类似)。
考虑move-assignment运算符的以下实现:
Class& operator=(Class&& rhs)
{
this->~Class(); // call destructor
new (this) Class(std::move(rhs)); // call move constructor in-place
}
有效以这种方式实现移动赋值运算符吗?也就是说,以这种方式调用析构函数和构造函数是否与语言中的任何对象生存期规则相冲突?
以这种方式实现移动赋值运算符是好主意吗?如果没有,为什么不,并且有更好的规范方式吗?
答案 0 :(得分:6)
它无效:如果移动子对象时调用此移动分配怎么办?然后你破坏了孩子(假设它有一个虚拟的析构函数)并在其位置重新创建一个父对象。
我会说,即使在非虚拟环境中,它仍然是一个坏主意,因为你不经常看到语法,这可能会使代码更难以为未来的维护者而烦恼。
最好的方法是避免必须完全编写自己的移动构造函数(并使用默认值),让所有的类成员都自己动手。例如,依赖unique_ptr等。如果失败的话,似乎在交换方面实现它(作为复制分配的复制和交换)将是一个容易理解的机制。
答案 1 :(得分:2)
(1)关于在自我移动案件中是否需要有效或移动是有争议的。 争论自我安全是代码应该是安全的(duh),我们当然希望自我分配是安全的。还有一些用户体验报告,对于许多使用移动的算法,自动移动是可能的,并且很难检查。
反对自我安全的立场是整个移动语义点是节省时间的位置,因此移动应该尽可能快。相对于搬家的成本,自动检查可能是昂贵的。请注意,编译器永远不会生成自动移动代码,因为自然(未转换)的rvalues不能自行移动。进行自我移动的唯一方法是使用" std :: move()"显式调用了cast。这给std :: move()的调用者带来了负担,要么验证自动移动还是不参与,要么说服自己不是这样。还要注意,创建用户定义的等效的" std :: move"将是微不足道的。检查自行,然后什么也没做。如果你不支持自行搬家,你可能想要记录下来。
(2)这不是一个模板,所以你可以知道表达式" new(this)Class(std :: move(rhs));"可以投掷。如果可以,那么不,这不是有效的。
(3)这段代码对维护者来说可能是一个难题,他们可能期望采用更传统的交换方法,但交换方法存在潜在的弊端。如果目标释放的资源需要尽快释放(例如互斥锁),则交换的缺点是资源被交换到移动源对象中。如果移动是调用" std :: move()"移动源对象可能不会立即被丢弃。 (因为这不是一个模板,你可以知道正在释放哪些资源。如果内存是唯一被释放的资源,那么这不是一个问题。)
更好的方法可能是从析构函数中分解资源释放代码,从移动构造函数中分解资源移动代码,然后在此移动赋值运算符中调用这些(内联)例程。