谁构build/devise了C ++的IOStreams,并且按照今天的标准来看,它还是会被认为是精心devise的?

首先,我似乎在征求主观意见,但那不是我所追求的。 我很想听到关于这个话题的一些有根据的论点。


为了对现代stream/序列化框架应该如何devise有所了解,我最近给了自己一本由Angelika Langer和Klaus Kreft编写的“ Standard C ++ IOStreams and Locales ”一书。 我认为,如果IOStreamsdevise不好,那么首先就不会把它变成C ++标准库。

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

尤其令我惊讶的是

  • 对于IOStreams的总体devise负责人似乎不得而知(我很想阅读关于这方面的一些背景信息 – 有没有人知道好的资源?

  • 一旦你钻研了IOStreams的直接表面,例如,如果你想用你自己的类来扩展IOStreams,你会遇到一个相当模糊getloc混淆的成员函数名称的接口,比如getloc / imbueuflow / underflowsnextc / sbumpc / sgetc / sgetnsgetn / pptr / epptr (可能还有更糟糕的例子)。 这使得了解整个devise以及单个部件如何合作变得非常困难。 即使我上面提到的书也没有那么多帮助(恕我直言)。


因此我的问题是:

如果你必须根据今天的软件工程标准来判断(如果实际上任何普遍的一致的话),那么C ++的IOSstream是否仍然被认为是精心devise的呢? (我不想从一般认为已经过时的东西中提高我的软件devise技能。)

几个不明智的想法进入了标准: auto_ptrvector<bool>valarrayexport ,仅举几例。 所以我不会把IOStream作为质量devise的标志。

IOStreams有一个方格的历史。 它们实际上是对早期的stream式库的改造,但是在当今许多C ++习惯用法不存在的时候被创作出来,所以devise师没有后见之明。 一个只是随着时间的推移而变得明显的问题是,由于大量使用虚拟function并以最细的粒度转发到内部缓冲区对象,所以几乎不可能像C的stdio一样有效地实现IOStream,而且还要感谢一些难以理解的奇怪在语言环境被定义和实现的方式中。 我记得这很模糊,我承认; 我记得几年前它是comp.lang.c ++。moderated的激烈辩论的主题。

关于他们的devise,最初的图书馆是由Bjarne Stroustrup创造的(不奇怪),然后由Dave Preston重新实现。 然后再由Jerry Schwartz为Cfront 2.0重新devise和重新实现,使用Andrew Koenig的操纵器。 该库的标准版本基于此实现。

源代码“C ++的devise和演变”,第8.3.1节。

如果你必须根据今天的软件工程标准来判断(如果实际上有任何普遍的一致的话),那么C ++的IOSstream是否仍然被认为是精心devise的呢? (我不想从一般认为已经过时的东西中提高我的软件devise技能。)

我会说, ,有几个原因:

糟糕的error handling

错误条件应该报告为exception,而不是operator void*

“僵尸对象”反模式是导致这样的错误 。

格式化和I / O之间分隔不好

这使得stream对象不必要的复杂,因为它们必须包含用于格式化的额外状态信息,不pipe你是否需要它。

这也增加了编写错误的几率:

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

相反,你写了类似的东西:

 cout << pad(to_hex(x), 8, '0') << endl; 

将不会有格式相关的状态位,并没有问题。

请注意,在像Java,C#和Python这样的“现代”语言中,所有对象都有一个由I / O例程调用的toString / ToString / __str__函数。 AFAIK,只有C ++通过使用stringstream作为转换为string的标准方式来做到这一点。

对i18n的支持不佳

基于Iostream的输出将string文字分割。

 cout << "My name is " << name << " and I am " << occupation << " from " << hometown << endl; 

格式化string将整个句子放入string文字中。

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

后一种方法更容易适应像GNU gettext这样的国际化图书馆,因为整个句子的使用为译者提供了更多的语境。 如果您的string格式化例程支持重新sorting(如POSIX $ printf参数),那么它还可以更好地处理不同语言之间的字词顺序差异。

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

执行input和输出(特别是input)是一个非常非常困难的问题,所以毫不奇怪,iostreams库里充满了各种各样的软件和事情,而且事后的工作可以做得更好。 但是在我看来,所有的I / O库,不pipe用什么语言,都是这样的。 我从来没有用过编程语言,因为I / O系统是美的东西,让我对devise师感到敬畏。 iostreams库确实具有优势,特别是在CI / O库(扩展性,types安全性等)方面,但我不认为任何人都把它作为一个很好的面向对象或通用devise的例子。

我对C ++ iostreams的看法随着时间的推移已经有了很大的改善,特别是在我开始通过实现我自己的stream类来扩展它们之后。 我开始欣赏可扩展性和总体devise,尽pipe成员函数的名字像xsputn或其他什么的可笑的。 无论如何,我认为I / Ostream是对C stdio.h的一个巨大的改进,它没有types安全,并且存在主要的安全缺陷。

我认为IOstream的主要问题是它们混淆了两个相关的但有点正交的概念:文本格式化和序列化。 一方面,IOstream被devise为产生对象的可读的,格式化的文本表示,另一方面,将对象序列化为便携式格式。 有时候这两个目标是一样的,但是有些时候这会导致一些严重的烦人的不协调。 例如:

 std::stringstream ss; std::string output_string = "Hello world"; ss << output_string; ... std::string input_string; ss >> input_string; std::cout << input_string; 

在这里,我们得到的input不是我们最初输出到stream中的。 这是因为<<运算符输出整个string,而>>运算符只会从stream中读取,直到遇到空白字符,因为没有长度信息存储在stream中。 所以即使我们输出一个包含“hello world”的string对象,我们也只是input一个包含“hello”的string对象。 所以虽然这个stream已经作为一个格式化工具的目的,但它没有正确序列化,然后反序列化对象。

你可能会说IOstream并不是被devise成序列化工具,但是如果是这样的话,那么inputstream真的是什么呢? 另外,在实践中,I / Ostream经常被用来序列化对象,因为没有其他标准的序列化设施。 考虑boost::date_timeboost::numeric::ublas::matrix ,其中如果使用<<运算符输出matrix对象,则在使用>>运算符input时将得到相同的精确matrix。 但是为了达到这个目的,Boost的devise者不得不将列数和行数信息作为文本数据存储在输出中,这就损害了实际的可读显示。 再次,文本格式设施和序列化的尴尬组合。

请注意大多数其他语言是如何区分这两个设施 例如,在Java中,通过toString()方法完成格式化,而通过Serializable接口完成Serializable

在我看来,最好的解决scheme应该是引入基于字节的stream,以及基于标准字符的stream。 这些stream将在二进制数据上运行,不需要考虑人们可读的格式/显示。 它们可以单独用作序列化/反序列化工具,将C ++对象转换为便携式字节序列。

我总是发现C ++ IOStreamsdevise不当:它们的实现使得很难正确定义一个新types的stream。 他们还混合iofunction和格式化function (考虑操纵器)。

个人而言,我所发现的最佳streamdevise和实现在于Ada编程语言。 它是一个解耦的模型,创build新types的stream的乐趣,输出函数总是工作,不pipe使用的stream是什么。 这是一个最不起眼的共同点:你输出字节到一个stream,就是这样。 stream函数负责将字节放入stream中,例如,将一个整数格式化为hex(当然,还有一组types属性,相当于为成员处理格式而定义的类成员)

我希望C ++是简单的关于stream…

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

  1. stream缓冲区:看看boost.iostream扩展:创buildgzip,tee,复制几行stream,创build特殊的filter等等。 没有它是不可能的。
  2. 本地化集成和格式化集成。 看看能做些什么:

     std::cout << as::spellout << 100 << std::endl; 

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

     std::cout << translate("Good morning") << std::endl; 

    可以根据std::cout的语言环境打印“Bonjour”或“בוקרטוב”!

    这样的事情可以做,只是因为iostreams非常灵活。

能做得更好吗?

当然可以! 事实上有很多事情可以改善

今天,从stream_buffer正确地派生是相当痛苦的,添加额外的格式化信息是非常不重要的,但是可能的。

但是回想很多年前,我仍然对图书馆的devise足够好,可以带来很多好玩的东西。

因为你不能总是看到大局,但是如果你留下了扩展点,那么即使你没有想到的点,它也会给你提供更好的能力。

(这个答案只是基于我的意见)

我认为IOStreams比它们的function相当复杂得多。 当我用C ++编写的时候,我仍然使用cstdio头文件来实现“旧式”I / O,我发现它更可预测。 在一个侧面说明,(虽然它并不重要,绝对时差是微不足道的)IOStreams已经被certificate在很多情况下比CI / O慢。

我不禁要回答问题的第一部分(谁做的?)。 但是在其他post中已经回答了。

至于问题的第二部分(精心devise?),我的回答是一个响亮的“不!”。 这里有一个让我不敢相信的例子:

 #include <stdint.h> #include <iostream> #include <vector> // A small attempt in generic programming ;) template <class _T> void ShowVector( const char *title, const std::vector<_T> &v) { std::vector<_T>::const_iterator iter; std::cout << title << " (" << v.size() << " elements): "; for( iter = v.begin(); iter != v.end(); ++iter ) { std::cout << (*iter) << " "; } std::cout << std::endl; } int main( int argc, const char * argv[] ) { std::vector<uint8_t> byteVector; std::vector<uint16_t> wordVector; byteVector.push_back( 42 ); wordVector.push_back( 42 ); ShowVector( "Garbled bytes as characters output oO", byteVector ); ShowVector( "With words, the numbers show as numbers.", wordVector ); return 0; } 

上面的代码由于iostreamdevise而产生废话。 由于某些原因,我不能把握把uint8_t字节视为字符,而更大的整数types则视为数字。 Qed糟糕的devise。

我也没办法想到解决这个问题。 该types可以是一个浮动或一个双而不是…所以一个强制转换为'int'使愚蠢iostream明白,数字不字符是主题不会帮助。

在收到我的回复的低票之后,可能还有更多的解释…… IOStream的devise是有缺陷的,因为它并没有给程序员提供一种方式来说明如何处理一个项目。 IOStream的实现会做出任意的决定(比如把uint8_t当作char而不是字节号)。 这是IOStreamdevise的一个缺陷,因为他们试图实现无法实现的目标。

C ++不允许对types进行分类 – 语言没有这个function。 没有is_number_type()或is_character_type()IOStream可以用来做出合理的自动select。 忽略这一点,并试图摆脱猜测是一个图书馆的devise缺陷。

承认,printf()同样无法在通用的“ShowVector()”实现中工作。 但这不是iostream行为的借口。 但是在printf()情况下,ShowVector()很可能是这样定义的:

 template <class _T> void ShowVector( const char *formatString, const char *title, const std::vector<_T> &v ); 

C ++ iostream有很多缺陷,正如其他答复中提到的那样,但是我想说明一些问题。

C ++在严重使用的语言中实际上是独一无二的,这使得初学者可以直接input和输出可变的input和输出。 在其他语言中,用户input往往涉及types强制或string格式化程序,而C ++则使编译器完成所有工作。 输出也是如此,尽pipeC ++在这方面不是唯一的。 尽pipe如此,您仍然可以在C ++中很好地进行格式化I / O,而无需理解类和面向对象的概念,这在教学上是有用的,而不必理解格式语法。 再说一遍,如果你在教初学者,这是一个很大的好处。

对于初学者来说,这种简单的代价是可以在更复杂的情况下处理I / O的麻烦,但是希望到那时程序员已经学会了足够的能力来处理它们,或者至less已经够老了喝。

使用IOStream时,我总是遇到惊喜。

图书馆似乎是面向文本而不是二进制的。 这可能是第一个惊喜:在文件stream中使用二进制标志不足以获得二进制行为。 上面的用户Charles Salvia已经正确地观察到了这一点:IOStreams在序列化方面(不需要信息丢失的地方)混合了格式化方面(你需要漂亮的输出,例如浮点数有限的数字)。 可能将这些方面分开是很好的做法。 Boost.Serialization做了这一半。 你有一个序列化函数路由到插件和提取器,如果你想。 你们之间已经有了这两方面的紧张关系。

许多函数也会混淆语义(例如get,getline,ignore和read,有些提取分隔符,有些则不要;还有一些是eof)。 进一步,在实现一个stream(例如xsputn,uflow,下溢)时,会提到一些奇怪的函数名称。 当使用wchar_t变体时情况会变得更糟。 wifstream做一个多字节转换,而wstringstream不转换。 二进制I / O无法使用wchar_t开箱即用:您已经覆盖了codecvt。

c缓冲的I / O(即FILE)不像C ++那样强大,但是更加透明,并且具有更less的反直觉行为。

每次当我偶然发现IOStream时,我都会像吸蛾一样吸引它。 如果一个聪明的人能够对整体架构有一个好看的话,可能会是一件好事。