C++ 代码中应该使用哪个 C I/O 库?[已结束]

Which C I/O library should be used in C++ code? [closed]

提问人:Ferruccio 提问时间:9/23/2008 最后编辑:CommunityFerruccio 更新时间:6/15/2015 访问量:13924

问:

在新的 C++ 代码中,我倾向于使用 C++ iostream 库而不是 C stdio 库。

我注意到一些程序员似乎坚持使用 stdio,坚持认为它更便携。

真的是这样吗?什么更好用?

C++ IOstream 标准

评论


答:

7赞 Adam Pierce 9/23/2008 #1

如果你像我一样,在学习C++之前学习过C,那么使用stdio库似乎更自然。iostream 与 stdio 各有利弊,但我在使用 iostream 时确实想念 printf()。

9赞 Colin Jensen 9/23/2008 #2

回到过去糟糕的日子里,C++标准委员会一直在搞语言,而iostreams是一个移动的目标。如果您使用 iostreams,那么您就有机会每年左右重写部分代码。正因为如此,我一直使用 stdio,自 1989 年以来没有显着变化。

如果我今天在做一些事情,我会使用 iostreams。

17赞 Mikael Jansson 9/23/2008 #3

这太啰嗦了。

思考用于执行以下操作的 iostream 构造(与 scanf 类似):

// nonsense output, just to examplify
fprintf(stderr, "at %p/%s: mean value %.3f of %4d samples\n",
    stats, stats->name, stats->mean, stats->sample_count);

这需要类似的东西:

std::cerr << "at " << static_cast<void*>(stats) << "/" << stats->name
          << ": mean value " << std::precision(3) << stats->mean
          << " of " << std::width(4) << std::fill(' ') << stats->sample_count
          << " samples " << std::endl;

在字符串格式化中,面向对象可以而且应该被回避,转而支持嵌入在字符串中的格式化 DSL。考虑一下 Lisp、Python 的 printf 样式格式,或者 PHP、Bash、Perl、Ruby 及其字符串插值。format

iostream因为这个用例充其量是错误的。

评论

12赞 Brad Wilson 9/23/2008
我真的想不出任何语言会“偷走”iostreams,这可能意味着它真的和每个人似乎认为的一样糟糕。:)
4赞 richq 9/23/2008
使用 -Wformat(带有 g++),您将获得 printf 等参数类型检查。
3赞 Skurmedel 8/22/2009
Brad:我很确定 Java 中的 Streams 受到 iostreams 的启发,至少是它们的可插拔性。
19赞 Sebastian Mach 11/24/2011
-1:您错误地使用了 iostreams。你应该对你的统计信息进行重载,然后客户端可以像 一样使用它,这显然胜过或超过必须接受 FILE 句柄的自定义打印函数。operator<<std::cout << stats;printf(...)
4赞 Calmarius 8/30/2012
即使你重载了<<你仍然需要将这个怪物写在它的函数体中。
40赞 11 revs, 2 users 98%Loki Astari #4

回答原始问题:
使用 stdio 可以完成的任何事情都可以使用 iostream 库完成。

Disadvantages of iostreams: verbose
Advantages    of iostreams: easy to extend for new non POD types.

C++ 比 C 向前迈出的一步是类型安全。

  • IOSTREAMS 被设计为显式类型安全。因此,对对象的赋值也显式检查了被赋值对象的类型(在编译器时)(如果需要,会生成编译时错误)。因此,可以防止运行时内存溢出或将浮点值写入 char 对象等。

  • 另一方面,scanf()/printf() 和 family 依赖于程序员获得正确的格式字符串,并且没有类型检查(我相信 gcc 有一个扩展可以提供帮助)。因此,它是许多错误的根源(因为程序员的分析不如编译器完美[并不是说编译器比人类更完美])。

只是为了澄清科林·詹森(Colin Jensen)的评论。

  • 自上一个标准发布以来,iostream 库一直很稳定(我忘记了实际年份,但大约 10 年前)。

澄清 Mikael Jansson 的评论。

  • 他提到的使用格式样式的其他语言有明确的保护措施,以防止 C stdio 库的危险副作用(在 C 语言中,但不是提到的语言)导致运行时崩溃。

注意我同意 iostream 库有点冗长。但我愿意忍受冗长,以确保运行时安全。但是我们可以通过使用 Boost 格式库来减轻冗长。

#include <iostream>
#include <iomanip>
#include <boost/format.hpp>

struct X
{  // this structure reverse engineered from
   // example provided by 'Mikael Jansson' in order to make this a running example

    char*       name;
    double      mean;
    int         sample_count;
};
int main()
{
    X   stats[] = {{"Plop",5.6,2}};

    // nonsense output, just to exemplify

    // stdio version
    fprintf(stderr, "at %p/%s: mean value %.3f of %4d samples\n",
            stats, stats->name, stats->mean, stats->sample_count);

    // iostream
    std::cerr << "at " << (void*)stats << "/" << stats->name
              << ": mean value " << std::fixed << std::setprecision(3) << stats->mean
              << " of " << std::setw(4) << std::setfill(' ') << stats->sample_count
              << " samples\n";

    // iostream with boost::format
    std::cerr << boost::format("at %p/%s: mean value %.3f of %4d samples\n")
                % stats % stats->name % stats->mean % stats->sample_count;
}

评论

0赞 Mikael Jansson 9/23/2008
你指的是什么崩溃?我不知道在C++中携带运行时信息(RTTI)的基元类型,那么这在哪些方面有所改善呢?
1赞 Martin York 9/23/2008
以下很容易崩溃:'char s[2];scanf(“%s”,s);'IOSTREAM 在编译时进行类型检查。因此,问题是在编译时发现的,而不是在运行时发现的。
7赞 rlbond 6/22/2009
iostreams 的另一个好处是它们可以处理从 std::iostream 派生的任何内容。因此,接受流的函数同样适用于控制台、文件或字符串输入。
1赞 Tom 12/23/2010
@rlbond - 适用于任何对象,可以从任何文件描述符创建。因此,它也同样适用于各种输入/输出机制:任何可以作为文件描述符实现的东西。fprintfFILE
0赞 Mohamed El-Nakeep 2/11/2014
@LokiAstari大多数编译器在编译时检查scanf和printf作为编译器扩展,尽管语言不需要它。
4赞 Evan 9/23/2008 #5

对于二进制 IO,我倾向于使用 stdio 的 fread 和 fwrite。对于格式化的东西,我通常会使用 IO Stream,尽管正如 Mikael 所说,non-trival(非默认?)格式可以是 PITA。

评论

0赞 Tom 1/19/2009
您还可以使用 istream::read() 和 ostream::write(),它们执行未格式化的 I/O。
1赞 Shadow2531 9/23/2008 #6

stdio 更适合读取二进制文件(例如将块转换为向量<无符号字符>并使用 .resize() 等)。有关示例,请参阅 http://nuwen.net/libnuwen.html 中 file.hh 中的 read_rest 函数。

C++ 流在读取二进制文件时可能会阻塞大量字节,从而导致错误的 eof。

评论

1赞 KeithB 9/23/2008
如果使用格式化的提取操作,则可能会出现空字节问题。但也有 read() 和 write() 方法,它们的工作方式就像 fread()/fwrite() 一样
1赞 Johannes Schaub - litb 12/14/2008
此外,您可以使用较低级别的 streambuf 接口,该接口也运行良好:istreambuf_iterator<char>it(file), e;vector<char> v(it, e);
14赞 KK. 9/23/2008 #7

Boost 格式库为 printf 样式的字符串格式设置提供了一种类型安全、面向对象的替代方案,并且是对 iostreams 的补充,由于巧妙地使用了运算符,因此不会出现通常的冗长问题。如果您不喜欢使用 iostream 的运算符进行格式化,我建议您考虑使用纯 C printf <<。

2赞 Jan de Vos 9/23/2008 #8

我使用 iostreams,主要是因为这样以后可以更轻松地摆弄流(如果我需要的话)。例如,你可能会发现你想在某个跟踪窗口中显示输出 -- 这对于 cout 和 cerr 来说相对容易做到。当然,你可以在 unix 上摆弄管道和其他东西,但这并不那么便携。

我确实喜欢类似 printf 的格式,所以我通常先格式化一个字符串,然后将其发送到缓冲区。对于Qt,我经常使用QString::sprintf(尽管他们建议使用QString:arg)。我也看过boost.format,但无法真正习惯语法(%太多)。不过,我真的应该看一看。

1赞 INS 9/23/2008 #9

由于 iostreams 已成为标准,因此您应该使用它们,因为您知道您的代码肯定会适用于较新版本的编译器。我想现在大多数编译器都非常了解 iostreams,使用它们应该没有任何问题。

但是如果你想坚持使用 *printf 函数,在我看来没有问题。

评论

1赞 Terminus 9/25/2008
不幸的是,它们依赖于 ABI,因此您不能将它们用于互连作为共享库开发的插件。
5赞 Dan Hewett 10/1/2008 #10

原则上我会使用 iostreams,在实践中我做了太多格式化的小数等,使 iostreams 太不可读,所以我使用 stdio。Boost::format 是一种改进,但对我来说还不够激励。在实践中,stdio 几乎是类型安全的,因为大多数现代编译器无论如何都会进行参数检查。

在这个领域,我仍然对任何解决方案都不完全满意。

评论

0赞 Tom 12/23/2010
+1 关于编译器和类型安全。如果没有像样的编译器支持(可破译的模板错误),没有人会使用 C++,因此保持 C 编译器达到相同的标准似乎是合理的。
0赞 EvilTeach 5/3/2011
您可以考虑创建一个 io 操纵器,它以您想要的方式格式化,而不会影响流。
2赞 12/14/2008 #11

我对 iolibraries 的怀念是格式化的输入。

IOTreates 没有很好的方法来复制 scanf(),甚至 boost 也没有输入所需的扩展名。

3赞 R Samuel Klatchko 11/23/2009 #12

虽然 C++ iostreams API 有很多好处,但一个重要的问题是围绕 i18n。问题在于,参数替换的顺序可能因区域性而异。典型的例子是这样的:

// i18n UNSAFE 
std::cout << "Dear " << name.given << ' ' << name.family << std::endl;

虽然这适用于英语,但在中文中,姓氏是第一位的。

在为国外市场翻译代码时,翻译代码片段充满了危险,因此新的 l10ns 可能需要更改代码,而不仅仅是更改不同的字符串。

boost::format 似乎结合了 stdio(一个单一格式字符串,可以按不同的顺序使用参数,然后它们出现)和 IOSTREAMS(类型安全、可扩展性)的优点。

评论

3赞 paxdiablo 12/23/2010
这实际上根本不是C++流的缺点,而是使用它们的代码的缺点。任何I/O 库也会遇到同样的问题。最好使用一种基于(例如)返回正确结果的方法来解决它。name.formal_name()name.culture
0赞 R Samuel Klatchko 1/3/2011
@paxdiablo - 不,许多 I/O 库可以通过无序插入参数来解决这个问题(boost::format 和现代风格的 stdio 都可以做到这一点)。虽然我原来的例子可以做得更好,但请考虑并发布诸如cout << student_name << " has a GPA of " << gpa;
1赞 Sebastian Mach 11/24/2011
THat 也是没有位置参数的 C 样式格式字符串的缺点。
0赞 Lothar 8/23/2017
将本地化混合到 C++ 流中是最大的错误之一。
5赞 Sebastian Mach 11/24/2011 #13

我将比较 C++ 标准库中的两个主流库。

不应在 C++ 中使用基于 C 样式格式字符串的字符串处理例程。

有几个原因可以减少它们的使用:

  • 非类型安全
  • 您不能将非 POD 类型传递给可变参数列表(即,既不能传递给 scanf+co.,也不能传递给 printf+co.)。 或者你进入了未定义行为的黑暗据点
  • 容易出错:
    • 您必须设法使格式字符串和“value-argument-list”保持同步
    • 您必须正确保持同步

在偏远地区引入的微妙错误

不仅printf本身不好。软件会变旧,需要重构和修改,并且可能会从远程位置引入错误。假设你有

.

// foo.h
...
float foo;
...

在某个地方......

// bar/frob/42/icetea.cpp
...
scanf ("%f", &foo);
...

三年后,您发现 foo 应该是某种自定义类型......

// foo.h
...
FixedPoint foo;
...

但是在某个地方......

// bar/frob/42/icetea.cpp
...
scanf ("%f", &foo);
...

...然后你的旧 printf/scanf 仍然会编译,只是你现在得到随机的段错误,你不记得为什么。

iostream 的详细程度

如果您认为 printf() 不那么冗长,那么您很可能没有充分利用他们的 iostream。例:

  printf ("My Matrix: %f %f %f %f\n"
          "           %f %f %f %f\n"
          "           %f %f %f %f\n"
          "           %f %f %f %f\n",
          mat(0,0), mat(0,1), mat(0,2), mat(0,3), 
          mat(1,0), mat(1,1), mat(1,2), mat(1,3), 
          mat(2,0), mat(2,1), mat(2,2), mat(2,3), 
          mat(3,0), mat(3,1), mat(3,2), mat(3,3));

将其与正确使用 iostreams 进行比较:

cout << mat << '\n';

你必须为 operator 定义一个适当的重载<<它大致具有 printf-thingy 的结构,但显着的区别在于你现在有一些可重用和类型安全的东西;当然,你也可以为类似 printf 的东西做一些可重用的东西,但随后你又有了 printf(如果你用新的替换矩阵成员呢?),除了其他非平凡的事情,例如你必须传递 FILE* 句柄。FixedPoint

对于 I18N,C 样式格式字符串并不比 iostream 更好

请注意,format-strings 通常被认为是国际化的救星,但在这方面它们并不比 iostream 好:

printf ("Guten Morgen, Sie sind %f Meter groß und haben %d Kinder", 
        someFloat, someInt);

printf ("Good morning, you have %d children and your height is %f meters",
        someFloat, someInt); // Note: Position changed.

// ^^ not the best example, but different languages have generally different
//    order of "variables"

也就是说,旧式 C 格式字符串和 iostream 一样缺乏位置信息。

您可能需要考虑 boost::format,它支持在格式字符串中显式声明位置。从他们的示例部分:

cout << format("%1% %2% %3% %2% %1% \n") % "11" % "22" % "333"; // 'simple' style.

一些 printf 实现提供了位置参数,但它们是非标准的。

不应该使用 C 样式格式的字符串吗?

除了性能(正如 Jan Hudec 所指出的),我看不出有什么原因。但请记住:

“我们应该忘记小效率,比如说大约 97% 的时间:过早优化是万恶之源。然而,我们不应该在关键的3%中放弃我们的机会。一个好的程序员不会被这样的推理所哄骗而沾沾自喜,他会明智地仔细研究关键的代码;但只有在识别出该代码之后“ - Knuth

“瓶颈发生在令人惊讶的地方,所以在你证明这就是瓶颈所在之前,不要试图进行二次猜测和速度黑客。”

是的,printf-implementations 通常比 iostreams 比 boost::format 快(来自我编写的一个小而具体的基准测试,但它应该在很大程度上取决于具体情况:如果 printf=100%,则 iostream=160%,并且 boost::format=220%)

但不要盲目地忽略思考:你到底花了多少时间在文本处理上?您的程序在退出之前运行多长时间? 回退到 C 样式格式字符串、松散类型安全、降低可重构性、 增加非常微妙的错误的可能性,这些错误可能会隐藏自己多年,并且可能只会正确地暴露自己 进入您最喜欢的客户面对?

就个人而言,如果我不能获得超过 20% 的加速,我不会退缩。但是因为我的应用程序 他们几乎把所有的时间都花在了字符串处理以外的其他任务上,我从来没有这样做过。一些解析器 我写的几乎把所有的时间都花在了字符串处理上,但他们的总运行时间太小了 这不值得测试和验证工作。

一些谜语

最后,我想预设一些谜语:

查找所有错误,因为编译器不会(他只能建议他是否很好):

shared_ptr<float> f(new float);
fscanf (stdout, "%u %s %f", f)

如果不出意外,这个有什么问题?

const char *output = "in total, the thing is 50%"
                     "feature  complete";
printf (output);