为什么 C++0x 中没有编译器生成的 swap() 方法?
C++ 编译器自动生成复制构造函数和复制赋值运算符。为什么不也交换
呢?
如今,实现复制赋值运算符的首选方法是复制和交换习惯用法:
T& operator=(const T& other)
{
T copy(other);
swap(copy);
return *this;
}
这种习惯用法的优点是在遇到异常时可以进行事务处理(假设 swap
实现不会抛出异常)。相反,编译器生成的默认复制赋值运算符会递归地对所有基类和数据成员进行复制赋值,并且不具有相同的异常安全保证。
同时,手动实现 swap
方法既繁琐又容易出错:
- 为了确保
swap
不会抛出异常,必须为类中和 in 中的所有非 POD 成员实现它。基类、非 POD 成员等。 - 如果维护者向类添加新的数据成员,则维护者必须记住修改该类的
swap
方法。如果不这样做可能会引入微妙的错误。此外,由于swap
是一种普通方法,因此如果swap
实现不完整,编译器(至少我知道没有)不会发出警告。
如果编译器自动生成 swap
方法不是更好吗?然后隐式复制分配实现就可以利用它。
显而易见的答案可能是:在开发 C++ 时,复制和交换习惯用法并不存在,现在这样做可能会破坏现有代码。
尽管如此,也许人们可以选择让编译器使用 C++0x 用于控制其他隐式函数的相同语法生成 swap
:
void swap() = default;
然后可能有规则:
- 如果有一个编译器 -生成的
swap
方法,可以使用复制和交换来实现隐式复制赋值运算符。 - 如果没有编译器生成的
swap
方法,则将像以前一样实现隐式复制分配运算符(对所有基类和所有成员调用复制分配)。
有谁知道这样的(疯狂的?)事情是否已被建议给 C++ 标准委员会,如果是的话,委员会成员有什么意见?
C++ compilers automatically generate copy constructors and copy-assignment operators. Why not swap
too?
These days the preferred method for implementing the copy-assignment operator is the copy-and-swap idiom:
T& operator=(const T& other)
{
T copy(other);
swap(copy);
return *this;
}
(ignoring the copy-elision-friendly form that uses pass-by-value).
This idiom has the advantage of being transactional in the face of exceptions (assuming that the swap
implementation does not throw). In contrast, the default compiler-generated copy-assignment operator recursively does copy-assignment on all base classes and data members, and that doesn't have the same exception-safety guarantees.
Meanwhile, implementing swap
methods manually is tedious and error-prone:
- To ensure that
swap
does not throw, it must be implemented for all non-POD members in the class and in base classes, in their non-POD members, etc. - If a maintainer adds a new data member to a class, the maintainer must remember to modify that class's
swap
method. Failing to do so can introduce subtle bugs. Also, sinceswap
is an ordinary method, compilers (at least none I know of) don't emit warnings if theswap
implementation is incomplete.
Wouldn't it be better if the compiler generated swap
methods automatically? Then the implicit copy-assignment implementation could leverage it.
The obvious answer probably is: the copy-and-swap idiom didn't exist when C++ was developed, and doing this now might break existing code.
Still, maybe people could opt-in to letting the compiler generate swap
using the same syntax that C++0x uses for controlling other implicit functions:
void swap() = default;
and then there could be rules:
- If there is a compiler-generated
swap
method, an implicit copy-assignment operator can be implemented using copy-and-swap. - If there is no compiler-generated
swap
method, an implicit copy-assignment operator would be implemented as before (invoking copy-assigment on all base classes and on all members).
Does anyone know if such (crazy?) things have been suggested to the C++ standards committee, and if so, what opinions committee members had?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
这是对特里答案的补充。
我们必须在 0x 之前在 C++ 中创建
swap
函数的原因是,一般的自由函数std::swap
效率较低(且通用性较差)。 。它制作了一个参数的副本,然后进行了两次重新分配,然后释放了本质上浪费的副本。制作重量级类的副本是浪费时间,因为我们作为程序员知道我们真正需要做的就是交换内部指针等等。然而,右值引用完全缓解了这个问题。在 C++0x 中,
swap
实现为:这更有意义。我们只是移动数据,而不是复制数据。这甚至允许交换不可复制的类型,例如流。 C++0x 标准草案规定,为了使用 std::swap 交换类型,它们必须是右值可构造的,并且右值可分配的(显然)。
此版本的
swap
本质上将执行任何自定义编写的交换函数将执行的操作。考虑一个我们通常为其编写swap
的类(例如这个“哑”向量):以前,
swap
会在丢弃之前对所有数据进行冗余副本之后。我们的自定义swap
函数只会交换指针,但在某些情况下使用起来可能很笨拙。在 C++0x 中,移动实现相同的最终结果。调用std::swap
会生成:翻译为:
编译器当然会删除多余的赋值,留下:
这正是我们自定义的
swap< /code> 首先会做。因此,虽然在 C++0x 之前我同意您的建议,但随着右值引用的引入,自定义交换实际上不再是必要的了。
std::swap
将在任何实现移动函数的类中完美工作。事实上,我认为实现
swap
函数应该成为不好的做法。任何需要交换函数的类也需要右值函数。但在这种情况下,根本不需要混乱的自定义交换
。代码大小确实增加了(两个 ravlue 函数与一个swap
),但右值引用不仅仅适用于交换,这给我们带来了积极的权衡。 (总体上代码更快,界面更干净,代码稍微多一点,没有更多交换
ADL麻烦。)至于我们是否可以
默认
右值函数,我不知道。我稍后会查一下,或者也许其他人可以插话,但这肯定会有帮助。 :)即便如此,允许
default
右值函数而不是swap
也是有意义的。所以本质上,只要它们允许= default
右值函数,你的请求就已经被提出了。 :)编辑:我做了一些搜索,
= default
移动的提案是提案n2583
。根据 this (我不不知道怎么读),就被“搬回来了”。它列在标题为“尚未准备好用于 C++0x,但开放以在将来重新提交”的部分下。所以看起来它不会成为 C++0x 的一部分,但可能会在以后添加。有点令人失望。 :(
编辑2:再环顾一下,我发现了这个: 定义移动特殊成员函数这是最近的事情,看起来我们确实可以默认
移动
耶!This is in addition to Terry's answer.
The reason we had to make
swap
functions in C++ prior to 0x is because the general free-functionstd::swap
was less efficient (and less versatile) than it could be. It made a copy of a parameter, then had two re-assignments, then released the essentially wasted copy. Making a copy of a heavy-weight class is a waste of time, when we as programmers know all we really need to do is swap the internal pointers and whatnot.However, rvalue-references relieve this completely. In C++0x,
swap
is implemented as:This makes much more sense. Instead of copying data around, we are merely moving data around. This even allows non-copyable types, like streams, to be swapped. The draft of the C++0x standard states that in order for types to be swapped with
std::swap
, they must be rvalue constructable, and rvalue assignable (obviously).This version of
swap
will essentially do what any custom written swap function would do. Consider a class we'd normally writeswap
for (such as this "dumb" vector):Previously,
swap
would make a redundant copy of all our data, before discarding it later. Our customswap
function would just swap the pointer, but can be clumsy to use in some cases. In C++0x, moving achieves the same end result. Callingstd::swap
would generate:Which translates to:
The compiler will of course get rid of redundant assignment's, leaving:
Which is exactly what our custom
swap
would have made in the first place. So while prior to C++0x I would agree with your suggestion, customswap
's aren't really necessary anymore, with the introduction of rvalue-references.std::swap
will work perfectly in any class that implements move functions.In fact, I'd argue implementing a
swap
function should become bad practice. Any class that would need aswap
function would also need rvalue functions. But in that case, there is simply no need for the clutter of a customswap
. Code size does increase (two ravlue functions versus oneswap
), but rvalue-references don't just apply for swapping, leaving us with a positive trade off. (Overall faster code, cleaner interface, slightly more code, no moreswap
ADL hassle.)As for whether or not we can
default
rvalue functions, I don't know. I'll look it up later or maybe someone else can chime in, but that would sure be helpful. :)Even so, it makes sense to allow
default
rvalue functions instead ofswap
. So in essence, as long as they allow= default
rvalue functions, your request has already been made. :)EDIT: I did a bit of searching, and the proposal for
= default
move was proposaln2583
. According to this (which I don't know how to read very well), it was "moved back." It is listed under the section titled "Not ready for C++0x, but open to resubmit in future ". So looks like it won't be part of C++0x, but may be added later.Somewhat disappointing. :(
EDIT 2: Looking around a bit more, I found this: Defining Move Special Member Functions which is much more recent, and does look like we can default
move
. Yay!当 STL 算法使用交换时,它是一个免费函数。有一个默认的交换实现:std::swap。它的作用显而易见。您似乎有这样的印象:如果您向数据类型添加交换成员函数,STL 容器和算法就会找到它并使用它。事实并非如此。
如果你能做得更好,你应该专门化 std::swap (在你的 UDT 旁边的命名空间中,所以它是由 ADL 找到的)。惯用做法是让它遵循成员交换函数。
当我们讨论这个主题时,在 C++0x 中将右值构造函数实现为交换也是惯用的(尽可能多地在这样的新标准上使用惯用语)。
是的,在一个成员交换是语言设计而不是自由函数交换的世界中,这意味着我们需要一个交换运算符而不是函数 - 否则原始类型(int、float 等)就不能'不能被一般对待(因为它们没有成员函数交换)。那么他们为什么不这样做呢?你必须询问委员会成员来确定 - 但我 95% 确定原因是委员会长期以来一直首选库实现功能,而不是发明新语法来实现功能。交换运算符的语法会很奇怪,因为与 =、+、- 等以及所有其他运算符不同,“交换”没有每个人都熟悉的代数运算符。
C++ 在语法上足够复杂。他们不遗余力地尽可能不添加新的关键字或语法功能,并且只有出于非常充分的理由才这样做(lambda!)。
swap, when used by STL algorithms, is a free function. There is a default swap implementation:
std::swap
. It does the obvious. You seem to be under the impression that if you add a swap member function to your data type, STL containers and algorithms will find it and use it. This isn't the case.You're supposed to specialize std::swap (in the namespace next to your UDT, so it's found by ADL) if you can do better. It is idiomatic to just have it defer to a member swap function.
While we're on the subject, it is also idiomatic in C++0x (in as much as is possible to have idioms on such a new standard) to implement rvalue constructors as a swap.
And yes, in a world where a member swap was the language design instead of a free function swap, this would imply that we'd need a swap operator instead of a function - or else primitive types (int, float, etc) couldn't be treated generically (as they have no member function swap). So why didn't they do this? You'd have to ask the committee members for sure - but I'm 95% certain the reason is the committee has long preferred library implementions of features whenever possible, over inventing new syntax to implement a feature. The syntax of a swap operator would be weird, because unlike =, +, -, etc, and all the other operators, there is no algebraic operator everyone is familiar with for "swap".
C++ is syntactically complex enough. They go to great lengths to not add new keywords or syntax features whenever possible, and only do so for very good reasons (lambdas!).
发送电子邮件给 Bjarne。他知道所有这些事情,通常会在几个小时内回复。
Send an email to Bjarne. He knows all of this stuff and usually replies within a couple of hours.
甚至编译器生成的移动构造函数/赋值是否已计划(使用 default 关键字)?
即使该习惯用法在发生异常时保持对象不变,但该习惯用法要求创建第三个对象,这是否首先会增加失败的可能性?
还可能会产生性能影响(复制可能比“分配”更昂贵),这就是为什么我没有看到如此复杂的功能由编译器来实现。
一般来说,我不会重载operator=,也不担心这种级别的异常安全:我不会将单独的赋值包装到try块中——我会如何处理最有可能的
std::bad_alloc
那时? - 所以我不在乎该物体在最终被摧毁之前是否仍保持原始状态。当然,在某些特定情况下,您可能确实需要它,但我不明白为什么应该在这里放弃“不使用的东西不付费”的原则。Is even compiler-generated move constructor/assignment planned (with the default keyword)?
Even though the idiom leaves the object unchanged in case of exceptions, doesn't this idiom, by requiring the creation of a third object, make chances of failure greater in the first place?
There might also be performance implications (copying might be more expensive than "assigning") which is why I don't see such complicated functionality being left to be implemented by the compiler.
Generally I don't overload operator= and I don't worry about this level of exception safety: I don't wrap individual assignments into try blocks - what would I do with the most likely
std::bad_alloc
at that point? - so I wouldn't care if the object, before they end up destroyed anyway, remained in the original state or not. There may be of course specific situations where you might indeed need it, but I don't see why the principle of "you don't pay for what you don't use" should be given up here.