谁设计/设计了C ++的IOStreams,并且按照当今的标准,它仍然被认为设计良好吗?

sta*_*ica 125 c++ iostream

首先,似乎我在征求主观意见,但这并不是我所追求的。我很想听听有关该主题的一些有充分根据的论点。


为了对如何设计现代流/序列化框架有所了解,我最近得到了Angelika Langer和Klaus Kreft撰写的《Standard C ++ IOStreams and Locales》一书的副本。我发现,如果IOStreams的设计不当,那么它就不会首先进入C ++标准库。

在阅读了本书的各个部分之后,我开始怀疑IOStreams是否可以从整体架构的角度与STL进行比较。阅读例如对Alexander Stepanov(STL的“发明人”)的采访,以了解有关STL的一些设计决策。

特别令我惊讶的是

  • 谁来负责IOStreams的总体设计似乎是个未知数(我很想阅读有关此的一些背景信息-有人知道好的资源吗?);

  • 一旦你钻研输入输出流,例如眼前表面之下,如果你想输入输出流用自己的类扩展,你会得到一个接口具有相当神秘和扑朔迷离的成员函数的名称,例如getloc/ imbueuflow/ underflowsnextc/ sbumpc/ sgetc/ sgetnpbase/ pptr/ epptr(和有可能甚至更糟的例子)。这使得了解整体设计以及单个零件如何协作变得更加困难。即使我上面提到的那本书没有帮助多(恕我直言)


因此,我的问题是:

如果你要判断今天的软件工程标准(如果确实对这些任何普遍同意),将C ++的输入输出流仍然被认为是经过精心设计?(我不想通过通常认为过时的方法来提高软件设计技能。)

小智 40

关于设计它们的人,原始的库(毫不奇怪)是由Bjarne Stroustrup创建的,然后由Dave Presotto重新实现的。然后,由Jerry Schwarz针对Cfront 2.0重新设计并重新实现,使用了来自Andrew Koenig的操纵器思想。库的标准版本基于此实现。

来源“ C ++的设计和演变”,第8.3.1节。

  • @Neil-坚果您对该设计有何看法?根据您的其他答案,很多人都希望听到您的意见。 (3认同)
  • 刚刚找到了Bjarne Stroustrup采访的笔录,其中提到了IOStreams的一些历史记录:http://www2.research.att.com/~bs/01chinese.html(此链接目前暂时断开,但您可以尝试使用Google的页面缓存) (2认同)
  • 更新的链接:http://www.stroustrup.com/01chinese.html。 (2认同)

Mar*_*tos 29

一些居心不良的想法找到自己的方式进入标准:auto_ptrvector<bool>valarrayexport,仅举几例。因此,我不会将IOStreams的存在必然地视为质量设计的标志。

IOStreams具有历史记录。它们实际上是对早期流库的重做,但是它们是在不存在当今许多C ++惯用语的时候编写的,因此设计人员没有后见之明。随着时间的推移,一个显而易见的问题是,由于大量使用了虚函数并以最精细的粒度转发到内部缓冲区对象,几乎不可能像C的stdio一样高效地实现IOStreams。以定义和实现语言环境的方式。我承认,我对此的记忆非常模糊。我记得几年前它在comp.lang.c ++。moderated上引起了激烈的争论。

  • 但是`auto_ptr`搞砸了复制/赋值语义,这使其成为取消引用错误的利基... (7认同)
  • @stakx:尽管如此,它已经被`unique_ptr`弃用并被更清晰,更强大的语义所取代。 (5认同)
  • @TokenMacGuy:它不是向量,也不存储布尔值。这使它有些误导。;) (5认同)
  • 谢谢您的意见。如果发现有价值的东西,我将浏览`comp.lang.c ++。moderated`存档,并在问题底部张贴链接。-另外,我不敢在`auto_ptr`上与您不同意:在阅读Herb Sutter的_Exceptional C ++ _之后,在实现RAII模式时,这似乎是一个非常有用的类。 (3认同)
  • @UncleBens`unique_ptr`需要右值引用。因此,此时auto_ptr是非常强大的指针。 (3认同)
  • @Matthieu M .:也就是说,这是当前标准下最好的解决方案。它的设计不是很差-您只需要知道它的工作方式即可。 (3认同)
  • @Billy:我更喜欢`boost :: scoped_ptr`语义,如果它的行为不像副本构造函数,我更喜欢不使用副本构造函数。您始终可以使用“交换”来交换(显式)其中两个或另一个恰当命名的方法的内容。 (3认同)
  • vector &lt;bool&gt;有什么可怕的? (2认同)
  • @TokenMacGuy,需要将其打包。反过来,这意味着并非其中的每个条目都具有唯一的内存地址,因此您不能给出对包含的布尔值的常规引用。Vector &lt;bool&gt;不应该进行位打包,而应该为BitPackedVectors创建一个特殊的类。 (2认同)

dan*_*n04 28

如果您必须根据当今的软件工程标准(如果实际上在这些标准上有任何一般性协议)来判断,那么C ++的IOStreams是否仍会被认为设计合理?(我不想通过通常认为过时的方法来提高软件设计技能。)

我会说“ 不”,原因如下:

错误处理差

错误情况应报告为例外情况,而不是operator void*

“僵尸对象”反模式是导致此类错误的原因。

格式化和I / O之间的间隔差

这使得流对象变得不必要复杂,因为无论是否需要,流对象都必须包含额外的状态信息以进行格式化。

它还增加了编写如下错误的几率:

using namespace std; // I'm lazy.
cout << hex << setw(8) << setfill('0') << x << endl;
// Oops!  Forgot to set the stream back to decimal mode.
Run Code Online (Sandbox Code Playgroud)

如果相反,您写了类似以下内容:

cout << pad(to_hex(x), 8, '0') << endl;
Run Code Online (Sandbox Code Playgroud)

没有格式相关的状态位,也没有问题。

请注意,像Java,C#和Python的“现代”的语言,所有的对象有一个toString/ ToString/ __str__函数是由I / O函数调用。AFAIK,只有C ++通过将其stringstream用作转换为字符串的标准方式来实现。

对i18n的支持不佳

基于Iostream的输出将字符串文字拆分为多个部分。

cout << "My name is " << name << " and I am " << occupation << " from " << hometown << endl;
Run Code Online (Sandbox Code Playgroud)

格式化字符串会将整个句子放入字符串文字中。

printf("My name is %s and I am %s from %s.\n", name, occupation, hometown);
Run Code Online (Sandbox Code Playgroud)

后一种方法更易于适应GNU gettext等国际化库,因为整个句子的使用为翻译人员提供了更多上下文。如果您的字符串格式化例程支持重新排序(例如POSIX $printf参数),那么它也可以更好地处理语言之间的单词顺序差异。

  • 从C ++ 11开始,它现在具有类型安全的varargs。 (5认同)
  • 实际上,对于i18n,替换应该由位置(%1,%2,..)标识,因为转换可能需要更改参数顺序。否则,我完全同意-+1。 (4认同)
  • @peterchen:那就是`printf`的POSIX`$`指定符。 (4认同)
  • 问题不在于格式字符串,而是C ++具有非类型安全的varargs。 (2认同)
  • 恕我直言,“额外状态信息”是最糟糕的问题。cout是全球性的;在其上附加格式设置标志会使这些标志成为全局标志,并且当您考虑到它们的大多数用途具有预期的几行范围时,那真是太糟糕了。可以通过绑定到ostream但保留其自身状态的'formatter'类来解决此问题。而且,与用printf进行的相同操作(可能的话)相比,用cout完成的操作通常看起来很糟糕。 (2认同)
  • 在C ++ 11中`std :: to_string`也是一回事。 (2认同)

小智 16

我将其作为单独的答案发布,因为这是纯粹的意见。

执行输入和输出(尤其是输入)是一个非常非常棘手的问题,因此毫不奇怪,iostreams库中充满了虚假的东西,事后看来,可以做得更好。但是在我看来,所有的I / O库,无论用哪种语言,都是这样的。我从来没有使用过一种编程语言,在这种语言中,I / O系统真是太漂亮了,这让我对它的设计师感到敬畏。iostreams库确实具有优势,特别是优于CI / O库(可扩展性,类型安全性等),但是我认为没有人将它作为出色的OO或通用设计的示例。


Cha*_*via 14

随着时间的推移,我对C ++ iostream的看法有了很大的改善,尤其是在我开始通过实现自己的流类来实际扩展它们之后。尽管成员函数的名称之类的荒谬可笑,我还是开始欣赏它的可扩展性和总体设计xsputn。无论如何,我认为I / O流是对C stdio.h的巨大改进,C stdio.h没有类型安全性,并且充斥着主要的安全缺陷。

我认为IO流的主要问题是它们将两个相关但有些正交的概念融合在一起:文本格式和序列化。一方面,IO流被设计为生成对象的人类可读的格式化文本表示形式,另一方面,将对象序列化为可移植格式。有时,这两个目标是相同的,但是有时,这会导致一些令人烦恼的不一致。例如:

std::stringstream ss;
std::string output_string = "Hello world";
ss << output_string;

...

std::string input_string;
ss >> input_string;
std::cout << input_string;
Run Code Online (Sandbox Code Playgroud)

在这里,我们作为输入获得的不是我们最初输出到流中的。这是因为<<运算符将输出整个字符串,而>>运算符将仅从流中读取直到遇到空格字符为止,因为流中没有存储长度信息。因此,即使我们输出包含“ hello world”的字符串对象,我们也仅将输入包含“ hello”的字符串对象。因此,尽管流已将其用作格式化工具,但未能正确序列化该对象,然后反序列化该对象。

您可能会说IO流并非设计为序列化工具,但如果是这种情况,输入流的真正用途是什么?此外,实际上,由于没有其他标准的序列化工具,I / O流通常用于序列化对象。考虑到boost::date_timeboost::numeric::ublas::matrix,如果您使用<<运算符输出矩阵对象,则在使用运算符输入矩阵对象时会得到相同的精确矩阵>>。但是为了实现此目的,Boost设计人员必须将列数和行数信息作为文本数据存储在输出中,这损害了实际的人类可读显示。同样,文本格式设置工具和序列化的笨拙组合。

注意大多数其他语言如何将这两种功能分开。例如,在Java中,格式化是通过toString()方法完成的,而序列化是通过Serializable接口完成的。

我认为,最好的解决方案是引入基于字节的流以及基于标准字符的流。这些流将对二进制数据进行操作,而无需考虑人类可读的格式/显示。它们可以仅用作序列化/反序列化工具,以将C ++对象转换为可移植字节序列。

  • 另外,实现二进制流还需要您实现新的流类和新的缓冲区类,因为格式问题并没有与std :: streambuf完全分开。因此,基本上,您要扩展的唯一内容是`std :: basic_ios`类。因此,在一条线中,“扩展”跨越为“完全重新实现”的领域,并且从C ++ I / O流功能创建二进制流似乎可以解决这一问题。 (4认同)

Adr*_*son 10

我总是发现C ++ IOStreams设计不当:它们的实现使正确定义流的新类型非常困难。它们还混合了io功能和格式化功能(想想操纵器)。

就个人而言,我发现的最佳流设计和实现在于Ada编程语言。它是去耦的模型,是创建新型流的一种乐趣,并且无论使用哪种流,输出功能始终有效。这要感谢一个最小公分母:您将字节输出到流,仅此而已。流函数负责将字节放入流中,而不是将整数格式化为十六进制(当然,定义了一组用于处理格式化的类型属性,与类成员等效)不是他们的工作。

我希望C ++对于流来说是如此简单...


Art*_*yom 10

我认为IOStreams设计在可扩展性和实用性方面非常出色。

  1. 流缓冲区:看一下boost.iostream扩展:创建gzip,Tee,在几行中复制流,创建特殊的过滤器等等。没有它是不可能的。
  2. 本地化集成和格式集成。看看可以做什么:

    std::cout << as::spellout << 100 << std::endl;
    
    Run Code Online (Sandbox Code Playgroud)

    可以打印:“一百”甚至:

    std::cout << translate("Good morning")  << std::endl;
    
    Run Code Online (Sandbox Code Playgroud)

    可以打印“ Bonjour”或“ ??????????” 根据注入的语言环境std::cout

    仅仅因为iostreams非常灵活,就可以完成这些事情。

可以做得更好吗?

当然可以!实际上,有许多事情可以改进...

今天,正确地从中派生是很痛苦的,stream_buffer添加额外的格式信息以流式传输是很重要的,但是可能的。

但是,回顾多年前,我仍然很满意图书馆的设计,即将带来许多好处。

因为您可能无法始终看到全局,但是如果您为扩展留点,即使您没有想到的点也可以为您提供更好的能力。

  • “可以做什么”不是很相关。您是一名程序员,只要有足够的努力,任何事情都可以完成。但是IOStreams使实现*可以完成的大部分*变得非常痛苦。通常,您因遇到麻烦而表现糟糕。 (15认同)
  • 关于第二点的示例为什么比仅仅使用诸如print(spellout(100));和print(translate(“ Good morning”));之类的东西更好,您能提供评论吗?好主意,因为这可以将格式和i18n与I / O分离。 (5认同)
  • 因为它可以根据语言翻译成流。即:`french_output &lt;&lt; translation(“早安”)`; `english_output &lt;&lt; translation(“早安”)`会给你:“ Bonjour早安” (3认同)
  • 与printf相比,当您需要使用一种语言执行'&lt;&lt;“ text” &lt;&lt; value'但使用另一种语言执行'&lt;&lt; value &lt;&lt;“ text”'时,本地化要困难得多-与printf相比 (3认同)