Во-первых, может показаться, что я прошу субъективного мнения, но это не то, что мне нужно. Очень хотелось бы услышать аргументированные аргументы на эту тему.
В надежде получить некоторое представление о том, как должна быть разработана современная структура потоков / сериализации, я недавно купил себе копию книги « Стандартные C ++ IOStreams and Locales » Анджелики Лангер и Клауса Крефта . Я полагал, что если бы IOStreams не был хорошо спроектирован, он бы вообще не попал в стандартную библиотеку C ++.
Прочитав различные части этой книги, я начинаю сомневаться, можно ли IOStreams сравнивать, например, с STL с общей архитектурной точки зрения. Прочтите, например, это интервью с Александром Степановым («изобретателем» STL), чтобы узнать о некоторых проектных решениях, которые вошли в STL.
Что меня особенно удивляет :
Кажется, неизвестно, кто отвечал за общий дизайн IOStreams (я хотел бы прочитать некоторую справочную информацию об этом - кто-нибудь знает хорошие ресурсы?);
Как только вы погрузитесь в непосредственную поверхность IOStreams, например, если вы хотите расширить IOStreams своими собственными классами, вы попадете в интерфейс с довольно загадочными и запутанными именами функций-членов, например
getloc
/imbue
,uflow
/underflow
,snextc
/sbumpc
/sgetc
/sgetn
,pbase
/pptr
/epptr
(и есть наверное даже худшие примеры). Это значительно усложняет понимание общей конструкции и взаимодействия отдельных частей. Даже книга , которую я упоминал выше , не помогает , что много (ИМХО).
Таким образом, мой вопрос:
Если бы вам пришлось судить по сегодняшним стандартам разработки программного обеспечения (если на самом деле существует какое-либо общее соглашение по ним), можно ли считать IOStreams C ++ хорошо разработанными? (Я бы не хотел улучшать свои навыки разработки программного обеспечения с помощью того, что обычно считается устаревшим.)
std::streambuf
является базовым классом для чтения и записи байтов, аistream
/ostream
предназначен для форматирования ввода и вывода, принимая указатель наstd::streambuf
его место назначения / источник.ostream foo(&somebuffer); foo << "huh"; foo.rdbuf(cout.rdbuf()); foo << "see me!";
Ответы:
Несколько непродуманных идей нашли свое отражение в стандарте:,
auto_ptr
иvector<bool>
,valarray
и этоexport
лишь некоторые из них. Поэтому я бы не стал воспринимать наличие IOStreams как признак качественного дизайна.У IOStreams неоднозначная история. На самом деле они представляют собой переработку более ранней библиотеки потоков, но были созданы в то время, когда многие из сегодняшних идиом C ++ не существовали, поэтому у дизайнеров не было возможности оглянуться назад. Одна проблема, которая стала очевидной только со временем, заключалась в том, что практически невозможно реализовать IOStreams так же эффективно, как stdio C, из-за обильного использования виртуальных функций и пересылки во внутренние буферные объекты даже с самой тонкой степенью детализации, а также из-за некоторой непостижимой странности. способом определения и реализации локалей. Признаюсь, я плохо помню об этом; Я помню, как несколько лет назад он был предметом интенсивных дебатов на comp.lang.c ++. Moderated.
источник
comp.lang.c++.moderated
архив и размещаю ссылки внизу своего вопроса, если найду что-нибудь ценное. - Кроме того, я осмелюсь с вами не согласитьсяauto_ptr
: после прочтения « Исключительного C ++» Херба Саттера он кажется очень полезным для реализации шаблона RAII.unique_ptr
более четкой и мощной семантикой.unique_ptr
требует ссылки на rvalue. Так что на данный моментauto_ptr
очень мощный указатель.auto_ptr
он испортил семантику копирования / присваивания, что сделало его нишей для разыменования ошибок ...Что касается того, кто их разработал, оригинальная библиотека (что неудивительно) была создана Бьярном Страуструпом, а затем переопределена Дэйвом Пресотто. Затем Джерри Шварц переделал ее и заново реализовал для Cfront 2.0, используя идею манипуляторов от Эндрю Кенига. Стандартная версия библиотеки основана на этой реализации.
Источник «Дизайн и эволюция C ++», раздел 8.3.1.
источник
Я бы сказал НЕТ по нескольким причинам:
Плохая обработка ошибок
Об условиях ошибки следует сообщать с исключениями, а не с помощью
operator void*
.Анти-шаблон «объект зомби» - вот что вызывает подобные ошибки .
Плохое разделение между форматированием и вводом-выводом
Это делает объекты потока ненужными сложными, поскольку они должны содержать дополнительную информацию о состоянии для форматирования, нужна она вам или нет.
Это также увеличивает вероятность написания таких ошибок, как:
Если бы вместо этого вы написали что-то вроде:
Не было бы битов состояния, связанных с форматированием, и никаких проблем.
Обратите внимание, что в «современных» языках, таких как Java, C # и Python, все объекты имеют функцию
toString
/ToString
/,__str__
которая вызывается процедурами ввода-вывода. AFAIK, только C ++ делает наоборот, используяstringstream
стандартный способ преобразования в строку.Плохая поддержка i18n
Вывод на основе Iostream разбивает строковые литералы на части.
Строки формата помещают целые предложения в строковые литералы.
Последний подход легче адаптировать к библиотекам интернационализации, таким как GNU gettext, потому что использование целых предложений дает переводчикам больше контекста. Если ваша подпрограмма форматирования строк поддерживает переупорядочение (например, параметры POSIX
$
printf), она также лучше справляется с различиями в порядке слов между языками.источник
$
спецификаторы POSIXprintf
.Я отправляю это как отдельный ответ, потому что это чистое мнение.
Выполнение ввода и вывода (особенно ввода) - очень и очень сложная проблема, поэтому неудивительно, что библиотека iostreams полна объектов и вещей, которые с идеальной ретроспективой можно было бы сделать лучше. Но мне кажется, что все библиотеки ввода-вывода на любом языке такие. Я никогда не использовал язык программирования, в котором система ввода-вывода была бы такой красивой вещью, которая заставляла меня трепетать перед ее создателем. Библиотека iostreams имеет преимущества, особенно по сравнению с библиотекой CI / O (расширяемость, безопасность типов и т. Д.), Но я не думаю, что кто-то считает ее примером отличного объектно-ориентированного или универсального дизайна.
источник
Мое мнение о C ++ iostreams значительно улучшилось со временем, особенно после того, как я начал фактически расширять их, реализуя свои собственные классы потоков. Я начал ценить расширяемость и общий дизайн, несмотря на смехотворно плохие имена функций-членов, вроде
xsputn
или что-то еще. Тем не менее, я считаю, что потоки ввода-вывода являются значительным улучшением по сравнению с C stdio.h, который не имеет безопасности типов и пронизан серьезными недостатками безопасности.Я думаю, что основная проблема с потоками ввода-вывода состоит в том, что они объединяют две взаимосвязанные, но несколько ортогональные концепции: текстовое форматирование и сериализацию. С одной стороны, потоки ввода-вывода предназначены для создания удобочитаемого форматированного текстового представления объекта, а с другой - для сериализации объекта в переносимый формат. Иногда эти две цели являются одними и теми же, но иногда это приводит к серьезным досадным несоответствиям. Например:
Здесь то, что мы получаем в качестве ввода, не является тем, что мы изначально выводили в поток. Это связано с тем, что
<<
оператор выводит всю строку, тогда как>>
оператор будет только читать из потока, пока не встретит пробельный символ, поскольку в потоке нет информации о длине . Таким образом, даже если мы выводим строковый объект, содержащий "hello world", мы собираемся ввести только строковый объект, содержащий "hello". Таким образом, хотя поток выполнил свою задачу как средство форматирования, ему не удалось должным образом сериализовать, а затем десериализовать объект.Вы можете сказать, что потоки ввода-вывода не были предназначены для использования в качестве средств сериализации, но если это так, то для чего на самом деле потоки ввода ? Кроме того, на практике потоки ввода-вывода часто используются для сериализации объектов, поскольку других стандартных средств сериализации нет. Рассмотрим
boost::date_time
илиboost::numeric::ublas::matrix
, где, если вы выводите матричный объект с помощью<<
оператора, вы получите такую же точную матрицу, когда вы введете ее с помощью>>
оператора. Но для этого разработчикам Boost приходилось хранить информацию о количестве столбцов и строк в виде текстовых данных на выходе, что ставит под угрозу фактическое удобочитаемое отображение. Опять же, неудобное сочетание возможностей текстового форматирования и сериализации.Обратите внимание, как большинство других языков разделяют эти два средства. В Java, например, форматирование выполняется с помощью
toString()
метода, а сериализация - с помощьюSerializable
интерфейса.На мой взгляд, лучшим решением было бы ввести потоки на основе байтов наряду со стандартными потоками на основе символов . Эти потоки будут работать с двоичными данными, не заботясь о удобочитаемом форматировании / отображении. Их можно использовать исключительно как средства сериализации / десериализации для преобразования объектов C ++ в переносимые последовательности байтов.
источник
std::char_traits
не может быть портативно специализированным, чтобы взятьunsigned char
. Однако есть обходные пути, поэтому я думаю, что расширяемость снова приходит на помощь. Но я думаю, что тот факт, что потоки на основе байтов нестандартны, является слабым местом библиотеки.std::streambuf
. Итак, единственное, что вы расширяете, - этоstd::basic_ios
класс. Итак, есть линия, в которой «расширение» переходит в территорию «полного переопределения», и создание двоичного потока из средств потока ввода-вывода C ++, кажется, приближается к этой точке.Мне всегда казалось, что C ++ IOStreams плохо спроектированы: их реализация очень затрудняет правильное определение нового типа потока. они также смешивают функции io и функции форматирования (подумайте о манипуляторах).
Лично я считаю, что лучший дизайн и реализация потока, которые я когда-либо находил, лежит на языке программирования Ada. это модель развязки, удовольствие создавать потоки нового типа, а функции вывода всегда работают независимо от используемого потока. это благодаря наименее общему знаменателю: вы выводите байты в поток, и все. потоковые функции заботятся о помещении байтов в поток, это не их задача, например, форматировать целое число в шестнадцатеричное (конечно, есть набор атрибутов типа, эквивалентный члену класса, определенному для обработки форматирования)
Я бы хотел, чтобы С ++ был таким же простым в отношении потоков ...
источник
Я считаю, что дизайн IOStreams великолепен с точки зрения расширяемости и полезности.
Интеграция локализации и интеграции форматирования. Посмотрите, что можно сделать:
Можно напечатать: «сто» или даже:
Может печатать "Bonjour" или "בוקר טוב" в зависимости от региона
std::cout
!Такое возможно только потому, что iostreams очень гибкие.
Можно ли сделать лучше?
Конечно могло! На самом деле есть много вещей, которые можно улучшить ...
Сегодня это довольно болезненно для правильного вывода
stream_buffer
, довольно нетривиально добавить дополнительную информацию о форматировании в поток, но возможно.Но, оглядываясь назад много лет назад, я по-прежнему считаю, что дизайн библиотеки был достаточно хорош, чтобы принести много полезного.
Потому что вы не всегда можете видеть общую картину, но если вы оставите очки для расширений, это даст вам намного лучшие способности даже в тех моментах, о которых вы даже не задумывались.
источник
print (spellout(100));
и.print (translate("Good morning"));
Это могло бы показаться хорошей идеей, поскольку это отделяет форматирование и i18n от ввода-вывода.french_output << translate("Good morning")
;english_output << translate("Good morning")
даст вам: "Bonjour Good morning"out << format("text {1}") % value
и это может быть переведено"{1} translated"
. Так что работает нормально;-)
.(Этот ответ основан только на моем мнении)
Я думаю, что потоки IOStream намного сложнее, чем их функциональные эквиваленты. Когда я пишу на C ++, я все еще использую заголовки cstdio для ввода-вывода «старого стиля», который я считаю гораздо более предсказуемым. Кстати, (хотя это на самом деле не важно; абсолютная разница во времени незначительна) IOStreams во многих случаях было доказано, что они медленнее, чем CI / O.
источник
sstringstream
. Думаю, скорость имеет значение, хотя это вторично.Я всегда сталкиваюсь с неожиданностями при использовании IOStream.
Библиотека кажется текстовой, а не двоичной. Это может быть первым сюрпризом: использование двоичного флага в файловых потоках недостаточно для получения двоичного поведения. Пользователь Charles Salvia, приведенный выше, заметил это правильно: IOStreams смешивает аспекты форматирования (где вам нужен красивый вывод, например, ограниченные цифры для чисел с плавающей запятой) с аспектами сериализации (где вы не хотите потери информации). Наверное, было бы хорошо разделить эти аспекты. Boost.Serialization делает эту половину. У вас есть функция сериализации, которая при желании направляет средства вставки и экстракторы. Между обоими аспектами уже есть напряжение.
Многие функции также имеют запутанную семантику (например, get, getline, ignore и read. Некоторые извлекают разделитель, некоторые нет; также некоторые устанавливают eof). Далее некоторые упоминают странные имена функций при реализации потока (например, xsputn, uflow, underflow). Ситуация становится еще хуже, если использовать варианты wchar_t. Wifstream выполняет преобразование в многобайтовый формат, а wstringstream - нет. Двоичный ввод-вывод не работает из коробки с wchar_t: у вас есть перезапись codecvt.
Буферизованный ввод-вывод c (то есть FILE) не так мощен, как его аналог C ++, но более прозрачен и имеет гораздо менее интуитивно понятное поведение.
Тем не менее, каждый раз, когда я натыкаюсь на IOStream, он меня привлекает, как мотылька, чтобы выстрелить. Наверное, было бы хорошо, если бы какой-нибудь действительно умный парень хорошо разбирался в архитектуре в целом.
источник
Не могу не ответить на первую часть вопроса (Кто это сделал?). Но ответ был дан в других сообщениях.
Что касается второй части вопроса (Хорошо продуман?), То я отвечу категоричным «Нет!». Вот небольшой пример, который заставляет меня годами недоверчиво качать головой:
Приведенный выше код производит ерунду из-за дизайна iostream. По некоторым причинам, не зависящим от меня, они обрабатывают байты uint8_t как символы, тогда как более крупные целочисленные типы обрабатываются как числа. Qed Плохой дизайн.
Я также не могу придумать, как это исправить. Тип также может быть float или double ... поэтому приведение к int, чтобы заставить глупый iostream понять, что числа, а не символы, не помогут.
После того, как мой ответ проголосовал против, возможно, еще несколько слов объяснения ... Дизайн IOStream ошибочен, поскольку он не дает программисту возможности заявить, КАК обрабатывается элемент. Реализация IOStream принимает произвольные решения (например, рассматривает uint8_t как символ, а не как число байта). Это недостаток дизайна IOStream, поскольку они пытаются достичь недостижимого.
C ++ не позволяет классифицировать тип - язык не имеет возможности. Не существует таких вещей, как is_number_type () или is_character_type (), которые IOStream мог бы использовать для разумного автоматического выбора. Игнорирование этого и попытка уйти от угадывания ЯВЛЯЕТСЯ недостатком дизайна библиотеки.
Допустим, printf () также не сможет работать в универсальной реализации ShowVector (). Но это не оправдание поведению iostream. Но очень вероятно, что в случае printf () ShowVector () будет определен следующим образом:
источник
uint8_t
является ЬурейеЙ для. Это на самом деле чугун? Тогда не обвиняйте iostreams в том, что они относятся к нему как к чучелу.num_put
фасет вместо оператора вставки потока.Как отмечалось в других ответах, у iostreams C ++ много недостатков, но я хотел бы отметить кое-что в его защиту.
C ++ практически уникален среди серьезно используемых языков, что упрощает ввод и вывод переменных для начинающих. В других языках пользовательский ввод обычно включает в себя приведение типов или средства форматирования строк, в то время как C ++ заставляет компилятор выполнять всю работу. То же самое в основном верно и для вывода, хотя C ++ в этом отношении не так уникален. Тем не менее, вы можете довольно хорошо выполнять форматированный ввод-вывод на C ++ без необходимости понимать классы и объектно-ориентированные концепции, что полезно с педагогической точки зрения, и без необходимости понимать синтаксис формата. Опять же, если вы обучаете новичков, это большой плюс.
Эта простота для новичков имеет свою цену, из-за которой работа с вводом-выводом в более сложных ситуациях может стать головной болью, но, надеюсь, к этому моменту программист научился достаточно, чтобы иметь дело с ними, или, по крайней мере, стал достаточно взрослым. пить.
источник