为什么 `basic_ios::swap` 只进行部分交换?

18
C++11 §27.5.4.2/21: void swap(basic_ios& rhs); 作用: 除了rdbuf()和rhs.rdbuf(),交换*this和rhs的状态。
这种局部交换有什么用处?
会引起问题吗?

Alf P. Steinbach提出了一个问题。难以置信。:| - Nawaz
这真是令人惊讶。如果我交换两个东西,我真的希望它们交换;如果我有一个错误并发现它没有交换 rdbuf,我会认为这是一个实现错误。 - GManNickG
确实令人惊讶。我检查了我拥有的第一个FinalDraft(n3092),它完全相同。我想知道它在C++03中是否完全相同,也许是遗留下来的痕迹? - Matthieu M.
2
这是否使swap与移动赋值“不一致”?或者将流移动会留下缓冲区吗?如果不一致,那么在通用代码中,如果有人假设对于任何类型Tswap(t1,t2)的最终结果与T t3(move(t1)); t1 = move(t2); t2 = move(t3);的最终结果相同,则可能会出现潜在问题。 - Steve Jessop
2个回答

22
你可以因此归咎于我。委员会曾试图进行更改(我想尝试了两次),但每次解决方案都会导致问题出现。
十年后,交换和移动语义被添加到我们的I/O系统中,但这并不是完美的适配。
请注意,basic_ios::swap是一个受保护的成员函数,而没有命名空间范围的变量。因此,只有从派生类(通常是istream/ostream)调用它。请注意,i/o_stream::swap也是受保护的,没有命名空间范围的变量。它们的规范是调用基类swap,然后交换任何本地数据(例如istream中的gcount)。
最后,在string/filestream级别上,您将获得您认为是“正常”的swap:公共成员和命名空间范围的变量。在该级别上,您有一个数据成员字符串/文件缓冲区(rdbuf),以及基类。在该级别上,swap仅交换基础和数据成员。
所有这些复杂性的特征在于,基类中的rdbuf()实际上是指向派生类的streambuf(basic_filebuf或basic_stringbuf)的自引用指针,这就是为什么不希望基类交换这些自引用指针的原因。
这使得基类的swap看起来很奇怪,但每个人都受到了保护,除了派生的客户端。随后,派生客户端的swap代码看起来非常简单。在派生级别上,swap被公开,并以公共客户端期望的方式运行。

一个类似的操作也用于移动构造和移动赋值操作。由于基类是虚拟基类,因此其构造函数不会被最直接派生的类调用,移动构造变得更加复杂。

这很有趣。看起来很奇怪。但最终它能够正常工作。;-)

稍作修正:

Alberto Ganesh Barbati 负责保护 swapi/ostream 级别上。他的决定非常正确,而我在最初设计中完全忽略了它。


所以,简而言之:基类不知道如何交换派生类的缓冲区,这就是为什么它将其留给派生类的原因?那么,在basic_streambuf中加入一个虚拟交换函数呢? - Xeo
我还没有对那个设计进行原型制作,但我猜它可能会起作用。然而,在派生级别上的规范看起来有点奇怪:交换你的基类但不交换你的数据成员。 - Howard Hinnant
看起来要么是基本规范,要么是派生规范在当前的iostreams实现中看起来很奇怪。顺便说一下,你有没有想过重新设计iostreams或完全用其他东西替换它们?我可以看出这会严重影响向后兼容性,但假设这不是问题... 委员会中是否有想法流传,例如包含在Boost或sth中? - Xeo
1
我个人在几年前花了一些时间研究替代的I/O系统。但它从未超越爱好的阶段。然而,我认为这里有潜力。我希望看到一些内部自然线程安全(像C语言),并以更多数据驱动的方式处理本地化(而不是算法驱动)的东西(像C语言)。但保留C++类型安全和潜在性能增强的类型安全绑定。回过头来看,我会选择使用Unicode并放弃对非Unicode编码的支持。但我不知道这方面是否有组织的努力。 - Howard Hinnant

2
我只有一个猜测的答案... 如果作者假设流可以使用内部缓冲区(例如char buffer[50]数据成员),那么这个规定是必要的,因为显然缓冲区的内容可能会被交换,但它们的地址不会改变。 我不知道是否实际上允许这样做。

你可以通过 rdbuf 函数替换缓冲区。 - Cheers and hth. - Alf
@AlfP.Steinbach:是的,但原始缓冲区从哪里来?它肯定是在某个地方分配的,我没有发现任何阻止我使用内部数组的东西。 - Matthieu M.
1
缓冲区是basic_streambuf<charT, traits>类型的对象,或者是派生自该类型。可以通过stream.rdbuf(pMyNewBuffer)进行替换。basic_ios析构函数的文档中说明不会销毁rdbuf()对象,因此完全可以将该对象作为直接成员,但类仍必须支持更改rdbuf()指针。 - Cheers and hth. - Alf
1
@AlfP.Steinbach:我并没有说你不能改变它。struct S { char buffer[64]; char* ptr; };,原始情况下ptr指向buffer,这样可以在仍然有内部缓冲区的情况下更改ptr - Matthieu M.
听起来委员会认为没有生命周期管理的灵活性比“正确”的交换更重要。你不能两者兼得,而且现在让rdbuf接受shared_ptr(或unique_ptr)而不是缓冲区已经太晚了。这将非常有用,因为您可以获得生命周期管理而无需进行动态分配。 - Steve Jessop
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接