为什么C ++ 0x中没有编译器生成的swap()方法?

C ++编译器自动生成拷贝构造函数和拷贝赋值操作符。 为什么不swap呢?

现在,实现复制赋值运算符的首选方法是复制和交换方式:

 T& operator=(const T& other) { T copy(other); swap(copy); return *this; } 

( 忽略使用按值传递的复制易用表单 )。

这个习惯用法的优点是在exception情况下是事务性的(假设swap实现不会抛出)。 相比之下,默认的编译器生成的复制赋值操作符recursion地对所有基类和数据成员进行复制赋值,并且不具有相同的exception安全保证。

同时,手动实现swap方法是很繁琐和容易出错的:

  1. 为了确保swap不会丢失,必须为所有非POD成员在类和基类中,在非POD成员中执行。
  2. 如果一个维护者向类中添加一个新的数据成员,维护者必须记得修改该类的swap方法。 如果不这样做可能会引入微妙的错误。 另外,因为swap是一个普通的方法,所以如果swap实现不完整,编译器(至less我不知道的)不会发出警告。

如果编译器自动生成swap方法不是更好吗? 然后隐式复制分配实现可以利用它。

显而易见的答案可能是:当开发C ++时,copy-and-swap成语不存在,现在这样做可能会破坏现有的代码。

不过,也许人们可以select让编译器使用C ++ 0x用于控制其他隐式函数的相同语法来生成swap

 void swap() = default; 

然后可能会有规则:

  1. 如果存在编译器生成的swap方法,则可以使用“复制和交换”来实现隐式复制分配操作符。
  2. 如果没有编译器生成的swap方法,将像以前一样实现隐式的复制分配操作符(在所有基类和所有成员上调用复制分配)。

有谁知道C ++标准委员会是否已经提出了这样的(疯狂的)事情,如果有的话,委员会成员有什么意见?

这是特里的答案。

我们必须在0x之前在C ++中进行swap函数的原因是因为一般的自由函数std::swap比效率低(而且通用性较差)。 它做了一个参数的副本,然后有两个重新分配,然后释放基本上是浪费的副本。 制作一个重量级的副本是浪费时间,当我们作为程序员知道我们真正需要做的就是交换内部指针和什么。

然而,右值引用完全解决了这个问题。 在C ++ 0x中, swap被实现为:

 template <typename T> void swap(T& x, T& y) { T temp(std::move(x)); x = std::move(y); y = std::move(temp); } 

这更有意义。 我们不是复制数据,而只是移动数据。 这甚至允许不可复制的types(比如stream)被交换。 C ++ 0x标准草案规定,为了将types与std::swap ,它们必须是右值可构造的,并且右值可赋值(显然)。

这个版本的swap将基本上做任何自定义的写交换function。 考虑一个我们通常会写的swap类(比如这个“哑”向量):

 struct dumb_vector { int* pi; // lots of allocated ints // constructors, copy-constructors, move-constructors // copy-assignment, move-assignment }; 

以前, swap会在我们所有数据的冗余副本之前丢弃。 我们的自定义swapfunction只是交换指针,但在某些情况下可能笨拙的使用。 在C ++ 0x中,移动达到了相同的最终结果。 调用std::swap会生成:

 dumb_vector temp(std::move(x)); x = std::move(y); y = std::move(temp); 

其转化为:

 dumb_vector temp; temp.pi = x.pi; x.pi = 0; // temp(std::move(x)); x.pi = y.pi; y.pi = 0; // x = std::move(y); y.pi = temp.pi; temp.pi = 0; // y = std::move(temp); 

编译器当然会摆脱多余的任务,留下:

 int* temp = x.pi; x.pi = y.pi; y.pi = temp; 

正是我们的定制swap将首先做的。 所以,虽然在C ++ 0x之前,我会同意你的build议,定制swap是不是真的有必要了,引入右值引用。 std::swap在任何实现移动函数的类中都能很好地工作。

实际上,我认为实施swapfunction应该成为不好的做法。 任何需要swap函数的类都需要右值函数。 但在这种情况下,根本就不需要自定义swap的混乱。 代码大小确实增加了(两个ravlue函数与一个swap函数相比),但是右值引用不仅仅适用于交换,而且给我们留下了正面的折衷。 (整体速度更快,界面更干净,代码稍多,不再需要swap ADL麻烦。)

至于我们是否可以default右值函数,我不知道。 我会稍后再来查看,也许别人可以join,但这肯定会有所帮助。 🙂

即使如此,允许default右值函数而不是swap有意义的。 所以实质上,只要它们允许= default右值函数,你的请求已经被创build。 🙂

编辑:我做了一些search,并build议= default移动提案n2583 。 根据这个 (我不知道怎样读得很好),它被“移回”了。 它列在“未准备好用于C ++ 0x,但将来可以重新提交”的章节中。 所以看起来它不会是C ++ 0x的一部分,但可能稍后添加。

有点令人失望。 🙁

编辑2:多看看,我发现这一点: 定义移动特殊成员函数是更近,看起来像我们可以默认move 。 好极了!

当由STLalgorithm使用时,交换是一个自由function 。 有一个默认的交换实现: std::swap 。 它显而易见。 你似乎觉得如果你添加一个交换成员函数到你的数据types,STL容器和algorithm会find它并使用它。 事实并非如此。

如果你能做得更好,你应该专注于std :: swap(在UDT旁边的命名空间,所以它是ADL发现的)。 成员交换function就是惯用的。

当我们谈论这个问题的时候,在C ++ 0x中也是习惯用法(尽可能在这样一个新的标准上有成语)来实现rvalue构造函数作为交换。

是的,在一个成员交换是语言devise而不是自由函数交换的世界里,这意味着我们需要一个交换操作符而不是一个函数 – 或者原始types(int,float等)couldn'不能一概而论(因为他们没有成员职能交换)。 那为什么他们不这样做? 你肯定会问委员会成员 – 但我95%肯定,原因是委员会长期以来首选图书馆实现的function,尽可能发明新的语法来实现一个function。 交换运算符的语法会很奇怪,因为不同于=,+, – 等等,以及所有其他运算符,没有任何人都熟悉的“交换”的代数运算符。

C ++在语法上足够复杂。 他们竭尽全力不要添加新的关键字或语法function,只有这样做是因为很好的原因(lambdas!)。

有没有人知道这样(疯狂?)的东西已经提交给C ++标准委员会

发送电子邮件给Bjarne。 他知道所有这些东西,通常在几个小时内回复。

甚至是编译器生成的移动构造函数/赋值计划(与默认关键字)?

如果存在编译器生成的交换方法,则可以使用“复制和交换”来实现隐式复制分配操作符。

即使这个成语在exception的情况下保持对象不变,这个成语是不是要求创build第三个对象,从而使失败的机会更大呢?

也可能会有性能影响(复制可能比“分配”更昂贵),这就是为什么我没有看到编译器要实现这样复杂的function。

一般来说,我不会重载operator =,我不担心这个级别的exception安全性:我不会将单个任务包装到try块中 – 那么我会怎么处理最可能的std::bad_alloc ? – 所以我不在乎这个物体在最终被摧毁之前是否仍然保持原状。 当然,在某些情况下,你可能确实需要它,但是我不明白为什么在这里应该放弃“你不支付你不使用的东西”的原则。