Почему потоки в C++?

Как вы все знаете, существуют библиотеки, использующие такие потоки, как iostream и fstream.

Мой вопрос:

  • Почему стримы? Почему они не остановились на функциях, подобных print, fgets и так далее (например)?

Им требуются свои собственные операторы << и >>, но все, что они делают, можно было бы реализовать в виде простых функций, таких как выше, а также функцию

printf("Hello World!");

для меня намного читабельнее и логичнее, чем

cout << "Hello World";

Я также думаю, что все эти строковые абстракции в C++ компилируются в (менее эффективные) стандартные вызовы функций в двоичном виде.


person oh boy    schedule 30.04.2010    source источник
comment
Эффективность на самом деле не проблема, так как 99% времени будет потрачено на те же вызовы ОС для печати на консоль.   -  person BlueRaja - Danny Pflughoeft    schedule 30.04.2010
comment
-1 эй, мальчик, я полагаю, ты новичок в мире C++. Как вы можете говорить, что он менее эффективен, если вы не знаете? Прежде чем критиковать, пожалуйста, учись, и будь скромнее.   -  person Vicente Botet Escriba    schedule 30.04.2010
comment
Кстати, Boost.Format (boost. org/doc/libs/1_42_0/libs/format/doc/format.html) предоставляет безопасный способ форматирования вывода, подобный printf.   -  person Void    schedule 30.04.2010


Ответы (14)


Потоки имеют лучшую безопасность типов.

Например, printf("%s", a); может пойти ужасно неправильно, если a является целым числом. cout << a; не имеет этой проблемы.

Другая проблема заключается в том, что потоки лучше соответствуют методологиям объектно-ориентированного проектирования.

Например, у вас есть простое приложение, которое записывает некоторый вывод, а затем вы хотите, чтобы вывод направлялся в файл, а не в консоль. С вызовами C вам придется заменить все вызовы printf вызовами fprintf и позаботиться о поддержании FILE* на этом пути. С потоками вы просто меняете конкретный класс потока, который вы используете, и все, большая часть кода остается такой же:

void doSomething(ostream& output)
{
   output << "this and that" << a;
}

doSomething(cout);
doSomething(ofstream("c:\file.txt"));
person shoosh    schedule 30.04.2010
comment
Конечно, современные компиляторы были расширены (не в соответствии со стандартами) для поддержки проверки типов для строк формата *printf. - person Yann Ramin; 01.05.2010
comment
С вызовами C вам придется заменить все вызовы printf на вызовы fprintf - ситуация не так уж плоха, например. вы можете сделать перенаправление потока freopen(output.txt,w,stdout); и избежать замены - person Tebe; 15.09.2012

Во-первых, он позволяет использовать объектную модель C++ для создания функций, которым все равно, что они пишут: в стандартный вывод, в файл или в сетевой сокет (если у вас есть сетевой сокет, производный от ostream). Например.

void outputFoo(std::ostream& os)
{
  os << "Foo!";
}

int main()
{
  outputFoo(std::cout);

  std::ofstream outputFile("foo.txt");
  outputFoo(outputFile);

  MyNetworkStream outputSocket;
  outputFoo(outputSocket);
}

И аналогично для входных потоков.

Потоки также имеют преимущество, когда речь идет о вводе и выводе объектов. Что произойдет, если вы захотите прочитать объект с помощью scanf?

MyObject obj;
scanf("??", &obj); // What specifier to use? 

Даже если бы существовал соответствующий спецификатор, как scanf узнал бы, как заполнить члены объекта? С потоками C++ вы можете перегрузить operator<< и написать

MyObject obj;
std::cin >> obj;

И это сработает. Аналогично для std::cout << obj, так что вы можете написать код сериализации объекта в одном месте и не беспокоиться об этом где-либо еще.

person Tyler McHenry    schedule 30.04.2010
comment
Сетевой сокет, производный от ostream, действительно то, что я хотел бы увидеть :) - person shoosh; 30.04.2010
comment
@шош boost::asio::ip::tcp::iostream? - person Cubbi; 30.04.2010

Потоки C++ типобезопасны. В C, если вы скажете:

double d = 1.23;
printf( "%d", d );

вы не гарантируете, что получите сообщение об ошибке, даже если преобразование неверно.

Кажется, вы задаете очень простые вопросы по С++. Какой учебник C++ вы используете, который не охватывает их?

person Community    schedule 30.04.2010

printf не является типобезопасным для одного. Интерфейс cout также является более универсальным, что позволяет использовать многие функции, недоступные в версии printf. Одним из замечательных примеров является то, что ваш оператор потока для ваших типов, если вы все делаете правильно, ссылается на std::ostream как на поток, а не на cout. Таким образом, вы можете изменить назначение вывода, просто используя другой поток. Чтобы сделать это с помощью printf, вам нужно сделать много зависящих от платформы перезаписей выходных дескрипторов.

Я также думаю, что все эти строковые абстракции в C++ компилируются в (менее эффективные) стандартные вызовы функций в двоичном формате.

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

person Edward Strange    schedule 30.04.2010

Помимо безопасности типов и полиморфности, потоки более переносимы. В некоторых системах printf с длинной требует «%d», а в некоторых системах — «%ld».

person John Gordon    schedule 30.04.2010
comment
Соответствие всегда требует %ld, но l ничего не делает на некоторых платформах. - person Potatoswatter; 30.04.2010
comment
Я изменил много сложного, сумасшедшего кода, чтобы обойти это и использовать потоки, даже не осознавая этого! - person John Gordon; 30.04.2010
comment
+1 однажды я долго терялся, пока не использовал отладчик для просмотра - person Tebe; 15.09.2012

Потоки могут быть объединены в цепочку

cout << "hello" << " " << "world"
person Mex    schedule 30.04.2010
comment
Так же могут быть и методы: out.print(hello).print().print(world) - person el.pescado; 30.04.2010

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

class foo { ... };

ostream &operator<<(ostream &ostr, const foo &f)
{
    ostr << ... how you want to print a foo ...;
    return ostr;
}

И теперь вы можете напечатать foo, как и все остальное:

int n = ...
foo f = ...

cout << n << ": " << f << endl;
person R Samuel Klatchko    schedule 30.04.2010

C++ IOStreams являются смехотворно неэффективными (в большинстве известных мне реализаций). Часто это не проблема, но когда это проблема, библиотека практически бесполезна. Также хорошо, что синтаксис не интуитивно понятен (и очень, очень многословен). Библиотека сложна и ее трудно расширять. Тоже не очень гибкий. По сравнению с чем-то вроде STL IOStreams действительно выглядит как страшный сон. Но это здесь, и мы застряли с этим.

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

C++ нуждалась в библиотеке ввода/вывода, которая была лучше, чем C. И в некоторых важных отношениях C++ IOStreams лучше. Как уже упоминалось, они безопасны для типов и расширяемы. Реализуя один оператор, я могу распечатать определяемый пользователем класс. Этого нельзя было сделать с printf. Мне также не нужно беспокоиться о неправильном определении спецификаторов формата и выводе мусора из-за отсутствия безопасности типов.

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

Библиотека IOStreams представляет собой компромисс между:

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

Библиотека не достигает всего этого, и я считаю, что сегодня, с нашим многолетним опытом работы с языком, мы могли бы разработать гораздо лучшую библиотеку. Но еще в середине 90-х, когда они искали библиотеку ввода-вывода для добавления, это было лучшее, что они могли найти.

person jalf    schedule 30.04.2010

Потоковый синтаксис с operator<< и operator>> обеспечивает хорошую конкатенацию.

printf("%d%d%d%d%d", a, b, c, d, e);

vs

cout << a << b << c << d << e;

При этом в первом подходе всегда нужно думать о типе, а в потоковом подходе тип указывать нельзя.

person Danvil    schedule 30.04.2010

Между прочим, C++ позволяет вам использовать printf. Так что, если вам это нравится, продолжайте и используйте его.

Потоки используются не только для записи вывода на консоль. Это универсальное решение для буферизации данных, которое можно применять ко всему: от вывода на экран до обработки файлов, сетевого трафика и интерфейсов устройств ввода. Ваши вещи могут записывать (или читать) потоки, не заботясь о том, куда на самом деле пойдут эти данные.

Допустим, у вас есть сложный объект, который вы хотите записать в консоль вывода, в файл журнала и во всплывающее окно отладки. Собираетесь ли вы написать три функции, которые делают почти одно и то же, или вы собираетесь написать одну функцию, которой вы передаете выходной поток?

person dash-tom-bang    schedule 30.04.2010

На самом деле

cout << "Test: " << test << endl;

кажется мне более интуитивным, чем

printf("Test: %d\n", test);

Если вы никогда раньше не видели printf, вы никак не узнаете, что он делает.

В любом случае,

print "Test: " + test

(на нескольких языках включая Python :() имеет намного больше смысла :)

person BlueRaja - Danny Pflughoeft    schedule 30.04.2010
comment
В Python тоже есть print "Test: %d" % test, и он очень нужен для более сложного форматирования. - person UncleBens; 30.04.2010
comment
@UncleBens: Верно, и очень жаль, что они решили использовать строки форматирования в стиле C, особенно требующие ввода текста для динамического языка... - person BlueRaja - Danny Pflughoeft; 30.04.2010
comment
Если вы никогда раньше не видели потоки C++, это еще хуже: вы думаете, что выполняете какое-то безумное смещение битов! - person el.pescado; 30.04.2010
comment
@BlueRaja Зеленый единорог: я думаю, что, например, printf(%-04d, test) более удобочитаем, чем эквивалент C++. - person el.pescado; 30.04.2010
comment
Вы можете использовать спецификатор %s для любого типа в Python. С другой стороны, print "Test: " + test не работает, если test является целым числом. - person UncleBens; 30.04.2010
comment
@el.pescado: это только смутит тех, кто писал на C, но не на C++. Принятие решения о разработке языка просто потому, что мы не хотим сбивать с толку пользователей, использующих другой язык, — это отличный способ повторить ошибки других языков (см., например, ужасные блоки switch в Java и C#). - person BlueRaja - Danny Pflughoeft; 30.04.2010

Я до сих пор использую printf, главным образом потому, что с его помощью легко контролировать формат вывода.

Для меня безопасность типов не является главным преимуществом здесь, потому что ее можно легко обнаружить с помощью теста, учитывая тот факт, что консольную программу гораздо легче тестировать, чем приложения на основе пользовательского интерфейса или веб-приложения. Если вы не выполняете тест, более серьезные ошибки могут в любом случае пройти через проверку времени компиляции.

Я также не согласен с другой причиной, по которой поток претензий более гибок из-за взаимозаменяемости. Это эквивалентно рекомендации использовать fprintf(fout,...) для взаимозаменяемости. Если вам нужно перенаправить вывод, используйте канал. Если вы находитесь в библиотеке, почему бы вам просто не вернуть строку и позволить вызывающей стороне решить, куда она пойдет?

person Codism    schedule 30.04.2010
comment
Используйте канал — не все команды похожи на приложения. Вернуть строку - не очень эффективно. - person shoosh; 30.04.2010
comment
шош, просто из любопытства, как вы можете вернуть строку в C из функции? Я имею в виду, что если бы он был размещен там как (static char *mystring), вы бы вернули только указатель. если вы имеете в виду строку из STL, вы возвращаете только объект, представляющий строку, строка хранится где-то еще - эффективность не снижается - person Tebe; 15.09.2012

stringstream безопаснее, чем snprintf/sscanf, потому что полностью исключает возможность переполнения буфера (даже "мягких сбоев").

person Potatoswatter    schedule 30.04.2010

Потоки работают с шаблонами, а printf/scanf — нет.

person UncleBens    schedule 30.04.2010