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

StackOverflow https://stackoverflow.com/questions/119098

  •  02-07-2019
  •  | 
  •  

Вопрос

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

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

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

Это было полезно?

Решение

Чтобы ответить на первоначальный вопрос:
Все, что может быть сделано с помощью 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 есть расширение, которое помогает).В результате это стало источником многих ошибок (поскольку программисты менее совершенны в своем анализе, чем компиляторы [не собираюсь говорить, что компиляторы совершенны, просто лучше людей]).

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

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

Чтобы прояснить комментарии Микаэля Янссона.

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

Н.Б. Я согласен, что библиотека iostream немного многословна.Но я готов мириться с многословием, чтобы обеспечить безопасность во время выполнения.Но мы можем уменьшить многословие, используя Библиотека расширенных форматов.

#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;
}

Другие советы

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

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

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

Тот Самый Библиотека расширенных форматов предоставляет типобезопасную объектно-ориентированную альтернативу форматированию строк в стиле printf и является дополнением к iostreams, которое не страдает от обычных проблем с подробностью из-за разумного использования operator%.Я рекомендую рассмотреть это вместо использования обычного C printf, если вам не нравится форматирование с помощью оператора iostream<<.

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

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

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

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

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

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

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

Вы не должны использовать процедуры обработки строк на основе C-style-format-string в C ++.

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

  • Не типизируется
  • Вы не можете передавать типы, отличные от POD, в списки переменных аргументов (т. Е. ни в scanf + co., ни в printf + co.), или вы попадаете в Темную Цитадель неопределенного поведения
  • Легко ошибиться:
    • Вы должны умудриться синхронизировать строку формата и "список значений-аргументов"
    • Вы должны поддерживать синхронизацию правильно

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

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

Детализация 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';

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

Строки формата в стиле 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.

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

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);

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

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

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

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

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

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

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

Чего мне не хватает в iolibrary, так это форматированного ввода.

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top