Лично я считаю, что это разумные вопросы, и я очень хорошо помню, что сам боролся с ними. Итак, здесь мы идем:
Где моя ошибка?
Я не назвал бы это ошибка но вы, вероятно, хотите, чтобы убедиться, что вы не должны отступать от того, что вы прочитали. То есть я бы выполнил три версии входных функций. В зависимости от того, насколько сложным является декодирование определенного типа, я, возможно, даже не разделяю код, потому что это может быть всего лишь небольшая часть. Если это больше, чем строка или две, вероятно, разделит код. То есть в вашем примере у меня будет экстрактор для FooBar
, который, по существу, читает Foo
или Bar
членов и соответственно инициализирует объекты. В качестве альтернативы, я бы прочитал ведущую часть, а затем позвонил в общую реализацию, извлекая общие данные.
Давайте сделаем это упражнение, потому что есть несколько вещей, которые могут быть осложнением. Из вашего описания формата мне не ясно, будет ли строка «строка» и что следует за строкой, например, пробелом (пробел, табуляция и т. д.). Если нет, вы не можете просто прочитать std::string
: поведение по умолчанию для них - читать до следующего пробела. Есть способы настроить поток на рассмотрение символов в виде пробелов (используя std::ctype<char>
), но я просто предполагаю, что есть пространство. В этом случае, экстрактор для Foo
может выглядеть следующим образом (обратите внимание, весь код полностью непроверенный):
std::istream& read_data(std::istream& is, Foo& foo, std::string& s) {
Foo tmp(s);
if (is >> get_char<'('> >> tmp.m_x >> get_char<','> >> tmp.m_y >> get_char<')'>)
std::swap(tmp, foo);
return is;
}
std::istream& operator>>(std::istream& is, Foo& foo)
{
std::string s;
return read_data(is >> s, foo, s);
}
Идея заключается в том, что read_data()
прочитать часть Foo
, которая отличается от Bar
при чтении FooBar
. Аналогичный подход будет использоваться для Bar
, но я опускаю это. Более интересный бит - использование этого забавного шаблона функции get_char()
. Это то, что называется манипулятором и является просто функцией, использующей ссылку на поток как аргумент и возвращающую ссылку на поток.Поскольку у нас есть разные символы, которые мы хотим прочитать и сравнить с ними, я сделал его шаблоном, но у вас также есть одна функция для каждого персонажа. Я просто слишком ленив, чтобы ввести его:
template <char Expect>
std::istream& get_char(std::istream& in) {
char c;
if (in >> c && c != 'e') {
in.set_state(std::ios_base::failbit);
}
return in;
}
То, что выглядит немного странно о моем коде, что есть несколько проверок, если все работает. Это потому, что поток просто установил std::ios_base::failbit
при прочтении члена, и я действительно не должен беспокоить себя. Единственный случай, когда есть добавленная специальная логика, находится в get_char()
, чтобы иметь дело с ожиданием определенного символа. Точно так же не происходит пропуски пробельных символов (т. Е. Использование std::ws
): все функции ввода - это функции formatted input
, и по умолчанию они пропускают пробелы (вы можете отключить это, используя, например, in >> std::noskipws
), но тогда многие вещи не будут работать ,
С подобной реализации для чтения Bar
, чтение FooBar
будет выглядеть примерно так:
std::istream& operator>> (std::istream& in, FooBar& foobar) {
std::string s;
if (in >> s) {
switch ((in >> std::ws).peek()) {
case '(': { Foo foo; read_data(in, foo, s); foobar = foo; break; }
case '[': { Bar bar; read_data(in, bar, s); foobar = bar; break; }
default: in.set_state(std::ios_base::failbit);
}
}
return in;
}
Этот код использует неформатированный ввод функцию, peek()
который просто смотрит на следующий символ. Он либо возвращает следующий символ, либо возвращает std::char_traits<char>::eof()
, если он терпит неудачу. Итак, если есть либо открывающая скобка, либо открывающая скобка, мы имеем read_data()
. В противном случае мы всегда терпим неудачу. Решила непосредственную проблему. On для распространения информации ...
Следует ли написать свои звонки оператору >> оставить исходные данные еще доступными после сбоя?
Общий ответ: нет. Если вы не прочитали что-то пошло не так, и вы сдаетесь. Это может означать, что вам нужно больше работать, чтобы избежать сбоев. Если вам действительно нужно отступить от позиции, на которой вы пытались проанализировать свои данные, вы можете сначала прочитать данные в std::string
с использованием std::getline()
, а затем проанализировать эту строку. Использование std::getline()
предполагает, что существует отличный символ для остановки. По умолчанию символ новой строки (отсюда и название), но вы можете использовать другие символы, а также:
std::getline(in, str, '!');
Эта остановка будет на следующей восклицательный знак и хранить все символы до этого в str
. Он также будет извлекать символ окончания, но он не сохранит его. Это иногда вызывает интерес, когда вы читаете последнюю строку файла, у которой может не быть новой строки: std::getline()
преуспевает, если он может читать хотя бы один символ. Если вам нужно знать, если последний символ в файле является символ новой строки, вы можете проверить, если поток достиг:
если (STD :: GetLine (в, ул) & & in.eof()) {зЬй: : cout < < "файл, не заканчивающийся на новую строку \"; }
Если да, то как я могу сделать это эффективно?
Потоки по самой своей природе одного прохода: вы получаете каждый символ только один раз, и если вы пропустите один, вы его уничтожаете. Таким образом, вы обычно хотите структурировать свои данные таким образом, чтобы вам не приходилось возвращаться. Тем не менее, это не всегда возможно, и большинство потоков фактически имеют буфер под капотом, два из которых могут быть возвращены. Поскольку потоки могут быть реализованы пользователем, нет никакой гарантии, что символы могут быть возвращены. Даже для стандартных потоков нет гарантии.
Если вы хотите вернуть персонажа, вы должны положить обратно точно характер извлеченный:
char c;
if (in >> c && c != 'a')
in.putback(c);
if (in >> c && c != 'b')
in.unget();
Последняя функция имеет несколько лучшую производительность, поскольку он не имеет, чтобы проверить, что персонаж действительно тот, который был извлечен. У него также меньше шансов потерпеть неудачу. Теоретически вы можете вернуть столько символов, сколько хотите, но большинство потоков не будет поддерживать больше, чем несколько во всех случаях: если есть буфер, стандартная библиотека позаботится об «отключении» всех символов до начала буфера . Если возвращается другой символ, он вызывает виртуальную функцию std::streambuf::pbackfail()
, которая может или не может сделать больше доступного пространства для буфера. В буферах потока, которые я реализовал, он обычно просто терпит неудачу, то есть я обычно не переопределяю эту функцию.
Если нет, существует ли способ «сохранить» (и восстановить) полный статус входного потока: состояние и данные?
Если вы хотите полностью восстановить состояние, в котором находитесь, включая символы, ответ: обязательно. ... но нет простой способ. Например, вы можете реализовать буфер потока фильтров и поместить обратно символы, как описано выше, для восстановления последовательности, которую нужно прочитать (или поддержки поиска или явной установки метки в потоке). Для некоторых потоков вы можете использовать поиск, но не все потоки поддерживают это. Например, std::cin
обычно не поддерживает поиск.
Восстановление персонажей - это только половина истории. Другие вещи, которые вы хотите восстановить, - это флаги состояния и любые данные форматирования. В самом деле, если поток пошел в неудавшееся или даже плохое состояние вам необходимо очистить государственные флаги, прежде чем поток будет делать большинство операций (хотя я думаю, что материал форматирования можно сбросить все равно):
std::istream fmt(0); // doesn't have a default constructor: create an invalid stream
fmt.copyfmt(in); // safe the current format settings
// use in
in.copyfmt(fmt); // restore the original format settings
Функция copyfmt()
копирует все поля, связанные с потоком, связанные с форматированием. К ним относятся:
- локаль
- в fmtflags
- iword хранения информации() и pword()
- события ручья
- Исключения
- потоков государственная
Если вы не знаете о большинстве из них, не волнуйтесь: большинство вещей вам, вероятно, не понравится около. Ну, пока вам это не понадобится, но к тому времени вы, надеюсь, приобрели некоторую документацию и прочитали об этом (или спросите и получите хороший ответ).
В чем разница между неисправностью и неисправностью? Когда мы должны использовать одно или другое?
Наконец короткий и простой:
failbit
устанавливается, когда ошибки форматирования будут обнаружены, например, ожидается число, но будет найден символ «T».
badbit
установлен, если что-то пойдет не так в инфраструктуре потока. Например, когда буфер потока не установлен (как в потоке fmt
выше), поток имеет std::badbit
. Другая причина заключается в том, что выбрано исключение (и поймано путем маски exceptions()
, по умолчанию все исключения пойманы).
Есть ли в Интернете ссылка (или книга), что объясняет глубоко, как бороться с iostreams? а не только основные вещи: полная обработка ошибок.
Ах, да, рад, что вы спросили. Вероятно, вы захотите получить «Стандартную библиотеку C++» Николая Йосуттиса. Я знаю, что в этой книге описаны все детали, потому что я способствовал ее написанию. Если вы действительно хотите знать все о IOStreams и локалях вы хотите Angelika Langer & Klaus Kreft's «IOStreams and Locales». В случае, если вы задаетесь вопросом, откуда я получил информацию изначально: это был «IOStreams» Стив Теала. Я не знаю, все ли в этой книге печатаются, и в ней не хватает многих материалов, которые были введены во время стандартизации. Так как я реализовал свою собственную версию IOStreams (и локалей), я знаю и о расширениях.
Возможно, Boost.Qi облегчит вашу жизнь для этой задачи. –
@KerrekSB: Boost.Qi то же самое, что Boost.Spirit? Если это так, я уже попробовал. Хотя я был поражен дизайном библиотеки, это значительно увеличило время моей компиляции до того, что я столкнулся со сбоем компилятора при некоторых условиях. Во всяком случае, мой вопрос больше связан с изучением передового опыта, чем с практическим решением. Однако, спасибо. – ereOn
Да, я думаю, что это то же самое, хотя я действительно не вижу в джунглях духа/кармы/ци/феникса/фьюжн ... и вы определенно хотите прекомпилировать заголовки с Boost, это точно! Ци просто поразила меня как довольно элегантное решение чего-то, что требует много ручного переосмысления колес в противном случае. –