Кто проектировал и разрабатывал С++ IOStreams, и будет ли он по-прежнему считаться хорошо разработанным сегодня стандартами?

Во-первых, может показаться, что я прошу субъективных мнений, но это не то, что мне нужно. Мне бы хотелось услышать некоторые обоснованные аргументы по этой теме.


В надежде получить некоторое представление о том, как должна быть разработана современная структура потоков/сериализации, Недавно я получил себе копию книги Стандартные IOStreams и локали С++ от Angelika Langer и Klaus Kreft. Я полагал, что если IOStreams не был хорошо разработан, он бы не попал в стандартную библиотеку С++.

После того, как я прочитал различные части этой книги, у меня возникают сомнения, если IOStreams могут сравниться, например. STL из общей архитектурной точки зрения. Чтение, например. это интервью с Александром Степановым (изобретателем STL), чтобы узнать о некоторых дизайнерских решениях, которые вошли в STL.

Что меня особенно удивляет:

  • Кажется неизвестным, кто отвечал за общий дизайн IOStreams (я бы хотел прочитать некоторую справочную информацию об этом - кто-нибудь знает хорошие ресурсы?);

  • Как только вы углубитесь в непосредственную поверхность IOStreams, например, если вы хотите расширить IOStreams своими собственными классами, вы получите интерфейс с довольно загадочными и запутанными именами функций-членов, например. getloc/imbue, uflow/underflow, snextc/sbumpc/sgetc/sgetn, pbase/pptr/epptr (и, возможно, еще худшие примеры). Это затрудняет понимание общего дизайна и взаимодействия отдельных частей. Даже книга, о которой я упоминал выше, не очень помогает (ИМХО).


Таким образом, мой вопрос:

Если бы вам пришлось судить по сегодняшним стандартам разработки программного обеспечения (если на самом деле существует какое-либо общее соглашение по этим вопросам), будут ли С++ IOStreams считаться хорошо продуманными? (Я бы не хотел улучшать свои навыки разработки программного обеспечения из того, что обычно считается устаревшим.)

Ответ 1

Несколько неправильных идей нашли свой путь в стандарте: auto_ptr, vector<bool>, valarray и export, просто чтобы назвать несколько. Поэтому я не стал бы использовать IOStreams в качестве признака качественного дизайна.

У IOStreams есть клетчатая история. Они на самом деле являются переработкой более ранней библиотеки потоков, но были созданы в то время, когда многие из сегодняшних идиом С++ не существовали, поэтому дизайнеры не имели преимущества задним числом. Одна из проблем, которая со временем стала очевидной, заключалась в том, что практически невозможно реализовать IOStreams так же эффективно, как C stdio, из-за обильного использования виртуальных функций и пересылки во внутренние объекты буфера даже при самой тонкой детализации, а также благодаря некоторой непостижимой странности в том, как определяются и реализуются локали. Я признаю, что эта память довольно нечеткая. Я помню, что это было предметом интенсивных дебатов несколько лет назад, на comp.lang.С++. Moderated.

Ответ 2

Что касается того, кто их спроектировал, оригинальная библиотека была (не удивительно) создана Бьярном Страуструпом, а затем переиздана Дэйвом Пресотто. Затем Джерри Шварц для Cfront 2.0 использовал новый дизайн и вновь реализовал его, используя идею манипуляторов от Эндрю Кенига. Стандартная версия библиотеки основана на этой реализации.

Источник "Дизайн и эволюция C++", раздел 8.3.1.

Ответ 3

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

Я бы сказал НЕТ по нескольким причинам:

Плохая обработка ошибок

Условия ошибки должны сообщаться с исключениями, а не с operator void*.

Анти-шаблон "зомби-объекта" является тем, что вызывает ошибки подобные этим.

Плохое разделение между форматированием и вводом/выводом

Это делает объекты потока ненужными сложными, поскольку они должны содержать дополнительную информацию о состоянии для форматирования, независимо от того, нужна она вам или нет.

Это также увеличивает вероятность написания ошибок, таких как:

using namespace std; // I'm lazy.
cout << hex << setw(8) << setfill('0') << x << endl;
// Oops!  Forgot to set the stream back to decimal mode.

Если вместо этого вы написали что-то вроде:

cout << pad(to_hex(x), 8, '0') << endl;

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

Обратите внимание, что на "современных" языках, таких как Java, С# и Python, все объекты имеют функцию toString/toString/__str__, которая вызывается подпрограммами ввода-вывода. AFAIK, только С++ делает это наоборот, используя stringstream в качестве стандартного способа преобразования в строку.

Плохая поддержка i18n

Выход на основе Iostream разбивает строковые литералы на куски.

cout << "My name is " << name << " and I am " << occupation << " from " << hometown << endl;

Строки формата помещают целые предложения в строковые литералы.

printf("My name is %s and I am %s from %s.\n", name, occupation, hometown);

Последний подход легче адаптировать к библиотекам интернационализации, таким как GNU gettext, потому что использование целых предложений предоставляет больше возможностей для переводчиков. Если ваша строковая процедура форматирования поддерживает переупорядочение (например, параметры POSIX $ printf), то она также лучше обрабатывает различия в порядке слов между языками.

Ответ 4

Я отправляю это как отдельный ответ, потому что это чистое мнение.

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

Ответ 5

Мое мнение о С++ iostreams значительно улучшилось со временем, особенно после того, как я начал фактически расширять их, внедряя свои собственные классы потоков. Я начал ценить расширяемость и общий дизайн, несмотря на смехотворные имена функций-членов, например xsputn или что-то еще. Несмотря на это, я думаю, что потоки ввода-вывода являются значительным улучшением по сравнению с C stdio.h, который не имеет безопасности типа и пронизан серьезными недостатками безопасности.

Я думаю, что основная проблема с потоками ввода-вывода заключается в том, что они объединяют две связанные, но несколько ортогональные концепции: текстовое форматирование и сериализация. С одной стороны, потоки ввода-вывода предназначены для создания удобочитаемого форматированного текстового представления объекта, а с другой стороны - для сериализации объекта в переносимом формате. Иногда эти две цели одно и то же, но в других случаях это приводит к некоторым серьезным неприятным несоответствиям. Например:

std::stringstream ss;
std::string output_string = "Hello world";
ss << output_string;

...

std::string input_string;
ss >> input_string;
std::cout << input_string;

Здесь то, что мы получаем в качестве входных данных, - это не то, что мы первоначально выводили в поток. Это связано с тем, что оператор << выводит всю строку, тогда как оператор >> будет считывать только из потока до тех пор, пока он не встретит символ пробела, так как в потоке нет информации о длине. Поэтому, хотя мы выводим строковый объект, содержащий "hello world", мы собираемся ввести строковый объект, содержащий "hello". Таким образом, хотя поток выполняет свою задачу как средство форматирования, он не смог правильно сериализовать и затем неэтериализовать объект.

Вы могли бы сказать, что потоки ввода-вывода не предназначены для обеспечения возможности сериализации, но если это так, для чего нужны потоки ввода? Кроме того, на практике потоки ввода-вывода часто используются для сериализации объектов, поскольку нет других стандартных средств сериализации. Рассмотрим boost::date_time или boost::numeric::ublas::matrix, где, если вы выведете матричный объект с оператором <<, вы получите точную матрицу при вводе ее с помощью оператора >>. Но для этого разработчикам Boost пришлось хранить данные счетчика столбцов и строк в виде текстовых данных на выходе, что ставит под угрозу фактический читаемый человеком дисплей. Опять же, неудобное сочетание текстовых средств форматирования и сериализации.

Обратите внимание, как большинство других языков разделяют эти два объекта. Например, в Java форматирование выполняется с помощью метода toString(), а сериализация выполняется через интерфейс Serializable.

На мой взгляд, лучшим решением было бы внедрение потоков на основе байт, наряду с потоками, основанными на стандартном символе. Эти потоки будут работать с двоичными данными, не заботясь о форматировании/отображении с возможностью чтения. Они могут использоваться исключительно как средства сериализации/десериализации, для перевода объектов С++ в переносимые байтовые последовательности.

Ответ 6

Я всегда обнаружил, что С++ IOStreams плохо разработаны: их реализация затрудняет правильное определение нового типа потока. они также сочетают функции и функции форматирования (подумайте о манипуляторах).

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

Я хочу, чтобы С++ был прост относительно потоков...

Ответ 7

Я думаю, что дизайн IOStreams блестящий с точки зрения расширяемости и полезности.

  • Буферы потоков: посмотрите на расширения boost.iostream: создайте gzip, tee, копии потоков в нескольких строках, создавать специальные фильтры и так далее. Без него это было бы невозможно.
  • Интеграция локализации и форматирование. Посмотрите, что можно сделать:

    std::cout << as::spellout << 100 << std::endl;
    

    Может печатать: "сто" или даже:

    std::cout << translate("Good morning")  << std::endl;
    

    Может печатать "Bonjour" или "בוקר טוב" в соответствии с языковой версией, пронизанной std::cout!

    Такие вещи могут быть выполнены только потому, что iostreams очень гибкие.

Может ли это быть сделано лучше?

Конечно, это возможно! На самом деле есть много вещей, которые можно было бы улучшить...

Сегодня довольно тяжело правильно выводить из stream_buffer, это довольно нетривиальный, чтобы добавить дополнительную информацию форматирования в поток, но возможно.

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

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

Ответ 8

(Этот ответ основан только на моем мнении)

Я думаю, что IOStreams намного сложнее, чем их эквиваленты функций. Когда я пишу на С++, я все еще использую заголовки cstdio для ввода/вывода старого стиля, которые я нахожу гораздо более предсказуемыми. На стороне примечания (хотя это не очень важно, абсолютная разница во времени пренебрежимо мала) IOStreams были доказаны во многих случаях медленнее, чем C I/O.

Ответ 9

Я всегда сталкиваюсь с неожиданностями при использовании IOStream.

Библиотека кажется текстовой, а не бинарной. Это может быть первым сюрпризом: использование двоичного флага в файловых потоках недостаточно для получения двоичного поведения. Пользователь Charles Salvia, приведенный выше, заметил это правильно: IOStreams смешивает аспекты форматирования (там, где вы хотите получить хороший результат, например, ограниченные цифры для float) с аспектами сериализации (где вы не хотите потери информации). Вероятно, было бы хорошо разделить эти аспекты. Boost.Serialization делает эту половину. У вас есть функция сериализации, которая отправляется к вставкам и экстракторам, если вы хотите. Там уже есть напряжение между обоими аспектами.

Многие функции также смешивают семантику (например, get, getline, игнорировать и читать. Некоторые извлекают разделитель, некоторые - нет, также некоторый набор eof). Далее некоторые упоминают имена странных функций при реализации потока (например, xsputn, uflow, underflow). Все становится еще хуже, когда вы используете варианты wchar_t. В wifstream выполняется перевод в многобайтовый, в то время как wstringstream - нет. Двоичный ввод-вывод не работает из коробки с помощью wchar_t: у вас есть перезапись кодека.

C буферизованный ввод-вывод (то есть FILE) не такой мощный, как его аналог на С++, но более прозрачен и имеет гораздо меньшее противодействие интуитивному поведению.

И все-таки каждый раз, когда я натыкаюсь на IOStream, меня привлекают к нему, как моль, чтобы стрелять. Вероятно, было бы хорошо, если бы какой-нибудь действительно умный парень хорошо посмотрел бы на общую архитектуру.

Ответ 10

Я не могу ответить на первую часть вопроса (кто это сделал?). Но на него ответили в других сообщениях.

Что касается второй части вопроса (хорошо спроектированный?), мой ответ звучит "Нет!". Вот небольшой пример, который заставляет меня качать головой с недоверием с годами:

#include <stdint.h>
#include <iostream>
#include <vector>

// A small attempt in generic programming ;)
template <class _T>
void ShowVector( const char *title, const std::vector<_T> &v)
{
    std::vector<_T>::const_iterator iter;
    std::cout << title << " (" << v.size() << " elements): ";
    for( iter = v.begin(); iter != v.end(); ++iter )
    {
        std::cout << (*iter) << " ";
    }
    std::cout << std::endl;
}
int main( int argc, const char * argv[] )
{
    std::vector<uint8_t> byteVector;
    std::vector<uint16_t> wordVector;
    byteVector.push_back( 42 );
    wordVector.push_back( 42 );
    ShowVector( "Garbled bytes as characters output o.O", byteVector );
    ShowVector( "With words, the numbers show as numbers.", wordVector );
    return 0;
}

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

Я также не могу придумать, чтобы исправить это. Тип мог бы также быть float или double вместо этого... так что приведение к 'int', чтобы сделать глупый iostream, понять, что цифры, а не символы, тема не поможет.

Получив голосование на мой ответ, возможно, еще несколько слов объяснения... Конструкция IOStream ошибочна, так как она не дает программисту средства указывать, КАК обрабатывается элемент. Реализация IOStream принимает произвольные решения (например, рассмотрение uint8_t как char, а не число байтов). Это недостаток дизайна IOStream, поскольку они пытаются достичь недостижимого.

С++ не позволяет классифицировать тип - язык не имеет объекта. Нет такой вещи, как is_number_type() или is_character_type(), который IOStream мог использовать, чтобы сделать разумный автоматический выбор. Игнорирование этого и попытка уйти с угадыванием - это недостаток дизайна библиотеки.

Допустим, printf() также не сможет работать в общей реализации "ShowVector()". Но это не повод для поведения iostream. Но очень вероятно, что в случае printf() ShowVector() будет определен следующим образом:

template <class _T>
void ShowVector( const char *formatString, const char *title, const std::vector<_T> &v );

Ответ 11

У С++ iostreams есть много недостатков, как указано в других ответах, но я хотел бы отметить что-то в его защите.

С++ практически уникален среди языков в серьезном использовании, что делает переменный ввод и вывод простым для начинающих. В других языках пользовательский ввод имеет тенденцию включать в себя принуждение типа или форматирование строк, в то время как С++ заставляет компилятор выполнять всю работу. То же самое в значительной степени относится к выводам, хотя С++ не столь уникальна в этом отношении. Тем не менее, вы можете хорошо форматировать ввод-вывод на С++, не понимая классов и объектно-ориентированных концепций, которые находятся на педагогической основе и не должны понимать синтаксис формата. Опять же, если вы учите новичков, это большой плюс.

Эта простота для новичков идет по цене, что может стать причиной головной боли для работы с I/O в более сложных ситуациях, но, надеюсь, к этому моменту программист достаточно научился, чтобы иметь возможность справиться с ними или, по крайней мере, достаточно устал, чтобы выпить.