为什么C ++ 0x中没有编译器生成的swap()方法?
C ++编译器自动生成拷贝构造函数和拷贝赋值操作符。 为什么不swap
呢?
现在,实现复制赋值运算符的首选方法是复制和交换方式:
T& operator=(const T& other) { T copy(other); swap(copy); return *this; }
( 忽略使用按值传递的复制易用表单 )。
这个习惯用法的优点是在exception情况下是事务性的(假设swap
实现不会抛出)。 相比之下,默认的编译器生成的复制赋值操作符recursion地对所有基类和数据成员进行复制赋值,并且不具有相同的exception安全保证。
同时,手动实现swap
方法是很繁琐和容易出错的:
- 为了确保
swap
不会丢失,必须为所有非POD成员在类和基类中,在非POD成员中执行。 - 如果一个维护者向类中添加一个新的数据成员,维护者必须记得修改该类的
swap
方法。 如果不这样做可能会引入微妙的错误。 另外,因为swap
是一个普通的方法,所以如果swap
实现不完整,编译器(至less我不知道的)不会发出警告。
如果编译器自动生成swap
方法不是更好吗? 然后隐式复制分配实现可以利用它。
显而易见的答案可能是:当开发C ++时,copy-and-swap成语不存在,现在这样做可能会破坏现有的代码。
不过,也许人们可以select让编译器使用C ++ 0x用于控制其他隐式函数的相同语法来生成swap
:
void swap() = default;
然后可能会有规则:
- 如果存在编译器生成的
swap
方法,则可以使用“复制和交换”来实现隐式复制分配操作符。 - 如果没有编译器生成的
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
会在我们所有数据的冗余副本之前丢弃。 我们的自定义swap
function只是交换指针,但在某些情况下可能笨拙的使用。 在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
在任何实现移动函数的类中都能很好地工作。
实际上,我认为实施swap
function应该成为不好的做法。 任何需要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
? – 所以我不在乎这个物体在最终被摧毁之前是否仍然保持原状。 当然,在某些情况下,你可能确实需要它,但是我不明白为什么在这里应该放弃“你不支付你不使用的东西”的原则。
- 什么`std :: kill_dependency`做,为什么我想要使用它?
- 是否有任何技巧使用std :: cin来初始化一个constvariables?
- 在C ++ 0x中优化掉“while(1);”
- 如果copy-list-initialization允许显式的构造函数,会出现什么问题?
- 什么时候在C ++ 11中的const引用比传递值更好?
- C ++ 0x和C ++ 11有什么区别?
- Visual Studio 2012中的C ++ 11function
- 在C ++ 11和Boost.Container下,vector :: resize(size_type n)的行为是否正确?
- 为什么我必须明确指定STLalgorithm函数的范围,即使我想在整个容器上工作?