Какую библиотеку ввода-вывода C следует использовать в коде C ++?

В новом коде C ++ я предпочитаю использовать библиотеку C ++ iostream вместо библиотеки C stdio.

Я заметил, что некоторые программисты придерживаются stdio, настаивая на его переносимости.

Так ли это на самом деле? Что лучше использовать?


person Ferruccio    schedule 23.09.2008    source источник


Ответы (13)


Чтобы ответить на исходный вопрос:
Все, что можно сделать с помощью stdio, можно сделать с помощью библиотеки iostream.

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

Шагом вперед, который C ++ сделал над C, стала безопасность типов.

  • iostreams был разработан с учетом явной типобезопасности. Таким образом, при назначении объекту явно проверяется тип (во время компиляции) присваиваемого объекта (при необходимости генерируется ошибка времени компиляции). Таким образом предотвращается переполнение памяти во время выполнения или запись значения с плавающей запятой в объект типа char и т. Д.

  • scanf () / printf () и семейство, с другой стороны, полагаются на то, что программист получит правильную строку формата, и не было никакой проверки типа (я считаю, что у gcc есть расширение, которое помогает). В результате он стал источником многих ошибок (поскольку программисты менее совершенны в своем анализе, чем компиляторы (не говоря уже о том, что компиляторы идеальны, просто лучше, чем люди)).

Просто чтобы прояснить комментарии Колина Дженсена.

  • Библиотеки iostream были стабильными с момента выпуска последнего стандарта (я забыл фактический год, но около 10 лет назад).

Для пояснения комментариев Микаэля Янссона.

  • Другие упомянутые им языки, использующие стиль формата, имеют явные меры предосторожности для предотвращения опасных побочных эффектов библиотеки C stdio, которые могут (в C, но не на упомянутых языках) вызвать сбой во время выполнения.

N.B. Я согласен с тем, что библиотека 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;
}
person Community    schedule 23.09.2008
comment
О каких сбоях вы говорите? Я не знал о примитивных типах, несущих информацию времени выполнения (RTTI) в C ++, так как это улучшает ситуацию? - person Mikael Jansson; 23.09.2008
comment
Легко вывести из строя следующее: 'char s [2]; scanf (% s, s);' iostream выполняет проверку типов во время компиляции. Итак, проблемы обнаруживаются во время компиляции, а НЕ во время выполнения. - person Martin York; 23.09.2008
comment
Еще одно преимущество iostreams в том, что они работают со всем, что унаследовано от std :: iostream. Таким образом, функция, принимающая поток, одинаково хорошо работает с вводом консоли, файла или строки. - person rlbond; 22.06.2009
comment
@rlbond - fprintf работает с любым FILE объектом, который может быть создан из любого файлового дескриптора. Таким образом, он также одинаково хорошо работает с различными механизмами ввода / вывода: со всем, что может быть реализовано как файловый дескриптор. - person Tom; 23.12.2010
comment
@LokiAstari большинство компиляторов проверяют scanf и printf во время компиляции как расширение компилятора, хотя это не требуется языком. - person Muhammad Annaqeeb; 11.02.2014
comment
Еще один серьезный недостаток iostreams - это i18n. - person Deduplicator; 05.02.2016
comment
@Deduplicator: Вы имеете в виду упорядочение слов в разных языках. - person Martin York; 05.02.2016

Это слишком многословно.

Подумайте о конструкции 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 format, форматирование в стиле printf в Python или PHP, Bash, Perl, Ruby и их строковую интраполяцию.

iostream для этого варианта использования в лучшем случае ошибочен.

person Mikael Jansson    schedule 23.09.2008
comment
Не согласен (см. Мой пост), fprintf () выполняет нулевую проверку входных параметров. Вы полагаетесь на имя, среднее значение, sample_count, чтобы быть конкретными типами. Любые изменения этих типов потребуют модификации каждой функции printf () в программе. - person Martin York; 23.09.2008
comment
Я действительно не могу вспомнить ни одного языка, который «украл» iostreams, что, вероятно, означает, что это действительно плохая идея, как все думают. :) - person Brad Wilson; 23.09.2008
comment
Используйте -Wformat (с g ++), и вы получите проверку типа аргумента printf и т. Д. - person richq; 23.09.2008
comment
Разве (void *) stats, приведенное во втором примере кода, не должно быть static_cast ‹void *› (stats) полностью на C ++? - person jk.; 25.09.2008
comment
Брэд: Я почти уверен, что Streams в Java вдохновлены iostreams, по крайней мере, их подключаемой природой. - person Skurmedel; 22.08.2009
comment
-1: вы неправильно используете iostreams. Вы должны были перегружать operator<< для своей статистики, тогда клиенты могут использовать ее как std::cout << stats;, что явно превосходит printf(...) или пользовательские функции печати, которые должны будут принимать ФАЙЛ-дескриптор. - person Sebastian Mach; 24.11.2011
comment
Даже если вы перегрузите ‹<, вам все равно нужно будет записать этого монстра в его тело функции. - person Calmarius; 30.08.2012
comment
@Calmarius: Но только один раз. Есть еще способ расширить printf. - person Sebastian Mach; 15.05.2014
comment
И лично мне printf кажется кошмаром, когда дело касается читабельности. Я предпочитаю тратить на несколько символов больше, когда пишу, и тратить гораздо меньше времени, когда читаю его после нескольких недель попыток понять, что я пытался сделать. - person Triskeldeian; 08.06.2016

Boost Format Library предоставляет типобезопасную объектно-ориентированную альтернативу форматированию строк в стиле printf и является дополнением к iostreams, которое не страдает от обычных проблем с многословием из-за умного использования оператора%. Я рекомендую подумать об использовании простого C printf, если вам не нравится форматирование с помощью оператора iostream ‹----------------.

person KK.    schedule 23.09.2008

В старые добрые времена комитет по стандартам C ++ возился с языком, и iostreams были движущейся целью. Если вы использовали iostreams, у вас была возможность переписывать части вашего кода каждый год или около того. Из-за этого я всегда использовал stdio, который существенно не изменился с 1989 года.

Если бы я делал что-то сегодня, я бы использовал iostreams.

person Colin Jensen    schedule 23.09.2008

Если, как и я, вы выучили C до того, как изучать C ++, библиотеки stdio кажутся более естественными в использовании. У iostream и stdio есть свои плюсы и минусы, но я пропускаю printf () при использовании iostream.

person Adam Pierce    schedule 23.09.2008

В принципе, я бы использовал iostreams, на практике я использую слишком много форматированных десятичных знаков и т. Д., Что делает iostreams слишком нечитаемыми, поэтому я использую stdio. Boost :: format - это улучшение, но для меня недостаточно мотивации. На практике stdio почти безопасен по типу, поскольку большинство современных компиляторов все равно проверяют аргументы.

Это область, в которой я все еще не совсем доволен ни одним из решений.

person Dan Hewett    schedule 30.09.2008
comment
+1 о компиляторах и безопасности типов. Никто не стал бы использовать C ++ без достойной поддержки компилятора (расшифровываемые ошибки шаблонов), поэтому соответствие компилятора C тем же стандартам кажется разумным. - person Tom; 23.12.2010
comment
Вы можете подумать о создании io-манипулятора, который форматирует так, как вы хотите, без влияния на поток. - person EvilTeach; 02.05.2011

Для двоичного ввода-вывода я обычно использую fread и fwrite из stdio. Для форматированного материала я обычно использую IO Stream, хотя, как сказал Микаэль, нетривиальное (нестандартное?) Форматирование может быть PITA.

person Evan    schedule 23.09.2008
comment
Вы также можете использовать istream :: read () и ostream :: write (), которые выполняют неформатированный ввод-вывод. В основном полезно, если вы выполняете сочетание форматированного и неформатированного ввода-вывода. - person Tom; 19.01.2009

Я буду сравнивать две основные библиотеки из стандартной библиотеки C ++.

Вы не должны использовать в C ++ подпрограммы обработки строк на основе строки формата C.

Существует несколько причин, чтобы смягчить их использование:

  • Не безопасный
  • Вы не можете передавать типы, отличные от POD, в списки аргументов с переменным числом аргументов (т.е. ни в scanf + co., Ни в printf + co.), Или вы входите в Темную цитадель неопределенного поведения.
  • Easy to get wrong:
    • You must manage to keep the format string and the "value-argument-list" in sync
    • Вы должны синхронизировать правильно

Незаметные ошибки, появившиеся в удаленных местах

Плох не только 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 все равно будет компилироваться, за исключением того, что теперь вы получаете случайные ошибки сегментации, и вы не помните, почему.

Многословие iostreams

Если вы думаете, что 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';

Вы должны определить правильную перегрузку для оператора < конечно, вы также можете сделать что-то повторно используемое для printf-like, но тогда у вас снова будет printf (что, если вы замените элементы матрицы на новый FixedPoint?), помимо других нетривиальных вещей, например вы должны передать дескрипторы FILE *.

Строки формата C для I18N не лучше, чем iostreams

Обратите внимание, что строки формата часто считаются спасением при интернационализации, но в этом отношении они ничуть не лучше 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 старого стиля не имеют позиционной информации так же, как и iostreams.

Вы можете рассмотреть возможность использования boost :: format. , который предлагает поддержку явного указания позиции в строке формата. Из их раздела примеров:

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

Некоторые реализации printf предоставляют позиционные аргументы, но они нестандартны.

Следует ли мне никогда использовать строки формата C?

Помимо производительности (как указал Ян Худек), я не вижу причин. Но имейте в виду:

«Мы должны забыть о небольшой эффективности, скажем, примерно в 97% случаев: преждевременная оптимизация - это корень всех зол. Тем не менее, мы не должны упускать наши возможности в этих критических 3%. Хорошего программиста такие рассуждения не убаюкивают, он поступит мудро, если внимательно взглянет на критический код; но только после того, как этот код будет идентифицирован »- Кнут

и

«Узкие места возникают в неожиданных местах, поэтому не пытайтесь усомниться в догадках и использовать спидхак, пока не докажете, что именно в этом узкое место». - щука

Да, реализации printf обычно быстрее, чем 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);
person Sebastian Mach    schedule 24.11.2011

Хотя у C ++ iostreams API много преимуществ, одна существенная проблема связана с i18n. Проблема в том, что порядок замены параметров может варьироваться в зависимости от культуры. Классический пример выглядит примерно так:

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

Хотя это работает для английского языка, в китайском языке фамилия стоит на первом месте.

Когда дело доходит до перевода вашего кода для зарубежных рынков, перевод фрагментов чреват опасностями, поэтому новые l10ns могут потребовать изменений в коде, а не только других строк.

boost :: format, кажется, сочетает в себе лучшее от stdio (одна строка формата, которая может использовать параметры в другом порядке, чем они появляются) и iostreams (безопасность типов, расширяемость).

person R Samuel Klatchko    schedule 23.11.2009
comment
На самом деле это вовсе не недостаток потоков C ++, а недостатка кода, который их использует. Любая библиотека ввода-вывода будет иметь ту же проблему. Было бы лучше решить с помощью метода name.formal_name(), который вернет правильный результат на основе name.culture (например). - person paxdiablo; 23.12.2010
comment
@paxdiablo - нет, многие библиотеки ввода-вывода могут решить эту проблему, интерполируя свои аргументы не по порядку (это могут делать как boost :: format, так и современные разновидности stdio). И хотя мой исходный пример можно было бы сделать лучше, рассмотрите и задайте такой вопрос, как cout << student_name << " has a GPA of " << gpa; - person R Samuel Klatchko; 03.01.2011
comment
Это также недостаток строк формата C-стиля, у которых нет позиционных аргументов. - person Sebastian Mach; 24.11.2011
comment
Смешивание локализации с потоками C ++ было одной из больших ошибок. - person Lothar; 23.08.2017

Я использую iostreams, в основном потому, что это упрощает работу с потоком позже (если мне это нужно). Например, вы можете узнать, что хотите отобразить вывод в каком-либо окне трассировки - это относительно легко сделать с помощью cout и cerr. Вы, конечно, можете возиться с конвейерами и прочим в unix, но это не так портативно.

Мне нравится форматирование в стиле printf, поэтому я обычно сначала форматирую строку, а затем отправляю ее в буфер. В Qt я часто использую QString :: sprintf (хотя они рекомендуют использовать QString :: arg). Я просмотрел boost.format тоже, но никак не мог привыкнуть к синтаксису (слишком много%). Хотя мне действительно стоит взглянуть на это.

person Jan de Vos    schedule 23.09.2008

В iolibraries мне не хватает форматированного ввода.

У iostreams нет удобного способа репликации scanf (), и даже у boost нет необходимого расширения для ввода.

person Community    schedule 13.12.2008

stdio лучше подходит для чтения двоичных файлов (например, преобразование блоков в вектор ‹unsigned char› и использование .resize () и т. д.). См. Функцию read_rest в file.hh в http://nuwen.net/libnuwen.html для пример.

Потоки C ++ могут подавиться большим количеством байтов при чтении двоичных файлов, вызывая ложный eof.

person Shadow2531    schedule 23.09.2008
comment
Если вы используете операции форматированного извлечения, у вас могут возникнуть проблемы с нулевыми байтами. Но есть также методы read () и write (), которые работают так же, как fread () / fwrite (). - person KeithB; 23.09.2008
comment
Кроме того, вы можете использовать интерфейс streambuf нижнего уровня, который тоже отлично работает: istreambuf_iterator ‹char› it (file), e; вектор ‹char› v (it, e); - person Johannes Schaub - litb; 13.12.2008

Поскольку iostreams стали стандартом, вы должны использовать их, зная, что ваш код наверняка будет работать с более новыми версиями компилятора. Думаю, в настоящее время большинство компиляторов очень хорошо знают iostreams, и проблем с их использованием возникнуть не должно.

Но если вы хотите использовать функции * printf, то, на мой взгляд, проблем быть не может.

person INS    schedule 23.09.2008
comment
К сожалению, они зависят от ABI, поэтому вы не можете использовать их для соединения плагинов, разработанных как разделяемые библиотеки. - person Terminus; 25.09.2008