提问人:stakx - no longer contributing 提问时间:5/2/2010 最后编辑:Machavitystakx - no longer contributing 更新时间:10/19/2022 访问量:18648
谁架构/设计了C++的IOStreams,按照今天的标准,它是否仍然被认为是精心设计的?[已结束]
Who architected / designed C++'s IOStreams, and would it still be considered well-designed by today's standards? [closed]
问:
首先,我似乎在征求主观意见,但这不是我所追求的。我很想听听关于这个话题的一些有根据的论点。
为了深入了解现代流/序列化框架应该如何设计,我最近给自己买了一本Angelika Langer和Klaus Kreft所著的《标准C++ IOStreams and Locales》一书。我想,如果IOStreams没有设计得好,它就不会首先进入C++标准库。
在阅读了本书的各个部分之后,我开始怀疑IOStreams是否可以从整体架构的角度与STL进行比较。例如,阅读对Alexander Stepanov(STL的“发明者”)的采访,了解STL的一些设计决策。
特别让我惊讶的是:
似乎不知道谁负责IOStreams的整体设计(我很想阅读一些关于这方面的背景信息——有人知道好的资源吗?
一旦你深入研究了IOStreams的直接表面,例如,如果你想用你自己的类来扩展IOSstreams,你就会得到一个具有相当神秘和令人困惑的成员函数名称的接口,例如/,//,//,//(可能还有更糟糕的例子)。这使得理解整体设计以及单个部件如何协同工作变得更加困难。即使是我上面提到的书也没有多大帮助(恕我直言)。
getloc
imbue
uflow
underflow
snextc
sbumpc
sgetc
sgetn
pbase
pptr
epptr
因此,我的问题:
如果你必须用今天的软件工程标准来判断(如果真的在这些标准上有任何普遍的共识),C++的IOStreams是否仍然被认为是设计良好的?(我不想从通常被认为过时的东西中提高我的软件设计技能。
答:
(这个答案只是基于我的意见)
我认为 IOStreams 比它们的等效功能复杂得多。当我用 C++ 编写时,我仍然使用 cstdio 标头进行“旧式”I/O,我发现这更可预测。顺便说一句,(虽然这并不重要;绝对时间差可以忽略不计)IOStreams 已被多次证明比 C I/O 慢。
评论
sstringstream
关于谁设计了它们,最初的库(毫不奇怪)是由 Bjarne Stroustrup 创建的,然后由 Dave Presotto 重新实现。然后,Jerry Schwarz 在 Cfront 2.0 中重新设计并重新实现了这一点,使用了 Andrew Koenig 的机械手的想法。该库的标准版本基于此实现。
资料来源“C++的设计与演进”,第8.3.1节。
评论
一些考虑不周的想法进入了标准:、 和 ,仅举几例。因此,我不会将 IOStreams 的存在视为质量设计的标志。auto_ptr
vector<bool>
valarray
export
IOStreams 有一个方格的历史记录。它们实际上是对早期流库的重新设计,但是是在当今许多 C++ 习语不存在的时候编写的,因此设计者没有事后诸葛亮的好处。随着时间的流逝,一个问题变得明显,那就是几乎不可能像 C 语言的 stdio 那样高效地实现 IOStreams,因为大量使用虚拟函数并以最细的粒度转发到内部缓冲区对象,并且由于语言环境的定义和实现方式存在一些难以理解的奇怪之处。我承认,我对此的记忆很模糊;我记得几年前,在comp.lang.c++.moderated上,它是激烈辩论的主题。
评论
comp.lang.c++.moderated
auto_ptr
unique_ptr
unique_ptr
auto_ptr
auto_ptr
我将其作为单独的答案发布,因为它是纯粹的意见。
执行输入和输出(尤其是输入)是一个非常非常困难的问题,因此毫不奇怪,iostreams库充满了麻烦和事后看来可以做得更好的事情。但在我看来,无论使用哪种语言,所有的 I/O 库都是这样的。我从来没有使用过一种编程语言,其中 I/O 系统是一件让我对它的设计者感到敬畏的美丽事物。iostreams 库确实有优势,特别是相对于 C I/O 库(可扩展性、类型安全等),但我认为没有人把它作为伟大的 OO 或通用设计的例子。
我总是发现C++ IOStreams设计不当:它们的实现使得正确定义新的A型流变得非常困难。它们还混合了 IO 功能和格式化功能(想想操纵器)。
就我个人而言,我发现的最好的流设计和实现在于 Ada 编程语言。它是解耦的模型,是创建新型流的乐趣,无论使用哪种流,输出功能始终有效。这要归功于一个最小的公分母:您将字节输出到流中,仅此而已。流函数负责将字节放入流中,例如将整数格式化为十六进制不是他们的工作(当然,有一组类型属性,相当于一个类成员,定义为处理格式)
我希望 C++ 在流方面如此简单......
评论
我认为IOStreams的设计在可扩展性和实用性方面非常出色。
- 流缓冲区:查看 boost.iostream 扩展:创建 gzip、tee、复制流 在几行中,创建特殊的过滤器等等。没有它,这是不可能的。
本地化集成和格式集成。看看可以做些什么:
std::cout << as::spellout << 100 << std::endl;
可以打印:“一百”甚至:
std::cout << translate("Good morning") << std::endl;
可以打印“Bonjour”或“בוקרטוב”,根据注入的区域设置!
std::cout
这些事情可以完成,因为 iostreams 非常灵活。
可以做得更好吗?
当然可以!事实上,还有很多事情可以改进......
今天,正确地从中推导是相当痛苦的,它是相当的
向流添加其他格式信息并非易事,但有可能。stream_buffer
但回想起很多年前,我仍然认为图书馆的设计足够好,可以带来很多好东西。
因为你不能总是看到大局,但如果你留下点来扩展它 即使在你没有想到的点上,也能给你更好的能力。
评论
print (spellout(100));
print (translate("Good morning"));
french_output << translate("Good morning")
english_output << translate("Good morning")
out << format("text {1}") % value
"{1} translated"
;-)
如果你必须根据今天的 软件工程标准(如果 实际上有任何一般 同意这些),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.
相反,如果你写了这样的东西:
cout << pad(to_hex(x), 8, '0') << endl;
不会有与格式相关的状态位,也没有问题。
请注意,在 Java、C# 和 Python 等“现代”语言中,所有对象都有一个由 I/O 例程调用的 // 函数。AFAIK,只有C++通过用作转换为字符串的标准方式来反其道而行之。toString
ToString
__str__
stringstream
对 i18n 的支持很差
基于 iostream 的输出将字符串文本拆分为多个部分。
cout << "My name is " << name << " and I am " << occupation << " from " << hometown << endl;
格式字符串将整个句子放入字符串文本中。
printf("My name is %s and I am %s from %s.\n", name, occupation, hometown);
后一种方法更容易适应像 GNU gettext 这样的国际化库,因为使用整个句子为翻译者提供了更多的上下文。如果您的字符串格式设置例程支持重新排序(如 POSIX printf 参数),那么它还可以更好地处理语言之间词序的差异。$
评论
$
printf
随着时间的推移,我对 C++ iostreams 的看法有了很大的改善,尤其是在我开始通过实现自己的流类来实际扩展它们之后。我开始欣赏它的可扩展性和整体设计,尽管成员函数名称(如)之类的可笑地糟糕。无论如何,我认为 I/O 流是对 C stdio.h 的巨大改进,C stdio.h 没有类型安全性,并且充满了重大的安全漏洞。xsputn
我认为 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;
在这里,我们作为输入得到的不是我们最初输出到流的内容。这是因为运算符输出整个字符串,而运算符只会从流中读取,直到遇到空格字符,因为流中没有存储长度信息。因此,即使我们输出一个包含“hello world”的字符串对象,我们也只会输入一个包含“hello”的字符串对象。因此,虽然流已起到了格式设置工具的作用,但它未能正确序列化对象,然后取消序列化对象。<<
>>
你可能会说 IO 流不是为序列化设施而设计的,但如果是这样的话,输入流的真正用途是什么?此外,在实践中,I/O 流通常用于序列化对象,因为没有其他标准序列化工具。考虑 或 ,其中,如果使用运算符输出矩阵对象,则在使用运算符输入矩阵时将获得相同的精确矩阵。但为了实现这一点,Boost设计人员必须将列数和行数信息作为文本数据存储在输出中,这会影响实际的人类可读显示。同样,文本格式设置工具和序列化的尴尬组合。boost::date_time
boost::numeric::ublas::matrix
<<
>>
请注意,大多数其他语言是如何分隔这两个设施的。例如,在 Java 中,格式化是通过该方法完成的,而序列化是通过接口完成的。toString()
Serializable
在我看来,最好的解决方案是引入基于字节的流,以及基于字符的标准流。这些流将对二进制数据进行操作,而不用担心人类可读的格式/显示。它们可以仅用作序列化/反序列化工具,将 C++ 对象转换为可移植字节序列。
评论
std::char_traits
unsigned char
std::streambuf
std::basic_ios
我忍不住回答问题的第一部分(谁干的?但它在其他帖子中得到了回答。
至于问题的第二部分(设计得好?),我的回答是响亮的“不!这里有一个小例子,多年来让我难以置信地摇了摇头:
#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 o.O", byteVector );
ShowVector( "With words, the numbers show as numbers.", wordVector );
return 0;
}
由于 iostream 设计,上面的代码会产生废话。由于一些我无法理解的原因,他们将uint8_t字节视为字符,而较大的整数类型则被视为数字。Q.e.d. 糟糕的设计。
我也想不出解决这个问题的方法。类型也可以是浮点数或双精度值......因此,强制转换为“int”以使愚蠢的 iostream 理解数字而不是字符是主题将无济于事。
IOStream的设计是有缺陷的,因为它没有给程序员一个说明如何处理一个项目的方法。IOStream 实现会做出任意决策(例如,将uint8_t视为字符,而不是字节号)。这是IOStream设计的一个缺陷,因为他们试图实现无法实现的目标。
C++ 不允许对类型进行分类 - 语言没有功能。IOStream 无法使用 is_number_type() 或 is_character_type() 来做出合理的自动选择。忽略这一点并试图摆脱猜测是库的设计缺陷。
诚然,printf() 同样无法在通用的“ShowVector()”实现中工作。但这并不是 iostream 行为的借口。但是在 printf() 的情况下,ShowVector() 很可能会这样定义:
template <class _T>
void ShowVector( const char *formatString, const char *title, const std::vector<_T> &v );
评论
uint8_t
num_put
方面而不是流插入运算符。
C++ iostreams有很多缺陷,正如其他响应中所指出的,但我想在它的防御中指出一些东西。
C++在大量使用的语言中几乎是独一无二的,它使初学者的可变输入和输出变得简单明了。在其他语言中,用户输入往往涉及类型强制或字符串格式化程序,而 C++ 使编译器完成所有工作。输出在很大程度上也是如此,尽管C++在这方面并不独特。尽管如此,您仍然可以在 C++ 中很好地完成格式化的 I/O,而无需理解类和面向对象的概念,这在教学上很有用,也不必理解格式语法。同样,如果你教的是初学者,那是一个很大的优势。
对于初学者来说,这种简单性是有代价的,这可能会使在更复杂的情况下处理 I/O 变得头疼,但希望到那时程序员已经学会了足够的知识来处理它们,或者至少已经足够老了可以喝酒了。
我在使用 IOStream 时总是会遇到意外。
该库似乎是面向文本的,而不是面向二进制的。这可能是第一个惊喜:在文件流中使用二进制标志不足以获得二进制行为。上面的用户 Charles Salvia 正确地观察到了这一点:IOStreams 将格式化方面(您想要漂亮的输出,例如浮点数的有限数字)与序列化方面(您不希望信息丢失)混合在一起。也许将这些方面分开会很好。Boost.Serialization 完成了这一半。您有一个序列化函数,如果需要,该函数可以路由到插入器和提取器。这两个方面之间已经有了紧张关系。
许多函数还具有令人困惑的语义(例如 get、getline、ignore 和 read。有些提取分隔符,有些则不提取;也有一些设置eof)。此外,有些人在实现流时提到了奇怪的函数名称(例如 xsputn、uflow、underflow)。当使用wchar_t变体时,情况会变得更糟。wifstream 执行到多字节的转换,而 wstringstream 则不执行。 二进制 I/O 不能开箱即用,wchar_t:您覆盖了编解码器。
c 缓冲 I/O(即 FILE)不如其 C++ 对应物强大,但更透明,反直觉行为更少。
尽管如此,每次当我偶然发现IOStream时,我都会像飞蛾扑火一样被它吸引。如果一些非常聪明的人能够好好看看整体架构,那可能是一件好事。
评论
std::streambuf
istream
ostream
std::streambuf
ostream foo(&somebuffer); foo << "huh"; foo.rdbuf(cout.rdbuf()); foo << "see me!";