我正在调整一些C ++ 03代码以利用C ++ 11的新功能,特别是以C ++ 11方式引入移动语义。但是我遇到了struct
,这让我很头疼,因为它包含一个匿名联盟。它具有全球形式
struct tree
{ // data of |tree|
enum {tag0,tag1, tag2, ... } kind;
union
{ type1 field1;
type2 field2;
...
};
// constructors
tree () : kind(tag0) {} // default, empty state
tree (const& type1 x) : kind(tag1), field1(x) {} // variant 1
tree (const& type2 x) : kind(tag2), field2(x) {} // variant 2
...
~tree(); // recursively clean up any branches
// other methods of |tree|
...
}; // |struct tree|
当然有更有意义的名字,但这里不是重点。结构体表示一种解析树,一种多重递归结构,可以在每个节点以不同的方式分支,从而使用union
。这么多成员类型实际上包含指向另一个tree
的指针;目前它们是原始指针(基本上是因为C ++ 03不允许除联盟中的POD类型之外的任何东西)由包含tree
管理,但现在C ++ 11不再有这样的限制,我当然计划用更多结构化的值替换它们(使用智能指针)。
(请不要开始说服我,我不应该首先使用工会;我认为这实际上是一个更自然的解决方案,而不是基于多态的问题,无论如何有一个足够大的代码使用它,我不想彻底改变这种方法。)
代码实际上已经定义了一种有限形式的移动语义(为了避免在自下而上构建树时必须始终进行深层复制),采用set_from
方法的形式复制到默认构造的(如此空的)tree
结构中,通过引用传递的另一个这样的结构的顶层(使用开关复制联合的正确活动)。在此之后,它在另一个中设置kind=tag0
,以防止与副本共享分支。将此方法转换为移动构造函数C ++ 11样式并不困难。
然而,拥有一个移动赋值运算符可能会很好。我想使用copy-and-swap idiom,这需要我编写交换操作。与this question中建议的不同,我认为使用std::swap
是不可能的,因为那将使用tree
的移动赋值运算符运算符,我试图定义;鸡与蛋的情况。所以我最好自己写一些东西。
难点在于,由于两个节点具有不同的变体,因此不存在在字段上调用swap
的问题。因为(1)union组件本身并不知道哪个变量是活动的,并且(2)union是匿名的,所以我甚至不能声明这样的操作,因此无法完成复制或交换整个两个节点的union组件。它必须在tree
结构的级别上才能定义交换。目前基于memcpy
的解决方案虽然很难实现,但是一旦联盟的字段不再是POD类型,它甚至不可能实现。
所以我有点想出好主意。我可以想到一个解决方案,对两个节点的活动变体进行两级切换,但我对前景并不感到兴奋。我想到的另一件事是交换角色,并直接定义移动分配(这与移动构造没有太大差别,但它要求在销毁任何后代时将目标设置为默认状态,就像调用析构函数一样,在移动源之前),然后使用临时,一次移动构造和两次移动分配以传统方式实现交换。具有讽刺意味的是,在这个解决方案中,移动分配是保证在其空变量中的节点,但移动分配必须处理更一般的情况。我要注意这个解决方案是否具有强大的异常安全性。
我有没有更容易解决的问题?
答案 0 :(得分:4)
所以不要使用复制和交换进行移动分配。 set_from
权利应该不是,因为它的全部意义在于避免分配资源。我假设它不是或者可以成为。
如果你还没有clear()
函数,那么使用从析构函数中获取的代码(释放资源)或set_from
来实现一个函数(这会使源变为清晰或清晰 - 国家)并提供强有力的例外保证。然后,此移动分配提供强大的异常保证:
tree &operator=(tree &&other) {
clear();
set_from(other);
return *this;
}
你需要移动构造和分配都使other
处于可以在其上调用set_from
的状态,所以要么确保这一点,要么在之后做一些额外的工作致电set_from
。 other.clear()
应该这样做,但可能有点矫枉过正。
其他一切都很顺利。只要你进行了移动构造和分配,std::swap
就会工作,但可能会略低于最佳状态。您可以通过(正如您已经确定的)std::swap
所使用的移动的目标来改进它,因为它们已经被移动了 - 从他们自己和你的移动实施使他们已经清楚。
您可能还想优化kind == other.kind
的情况,因此所需的只是相关字段的swap
。