2010-11-11 2 views
3

Я выполнил процедуру десериализации для объекта, используя оператор потока <<. Сама процедура использует istreambuf_iterator<char> для извлечения символов из потока один за другим, чтобы построить объект.Смутно об использовании `std :: istreambuf_iterator`

В конечном счете, моя цель - иметь возможность итерации по потоку с использованием istream_iterator<MyObject> и вставки каждого объекта в vector. Довольно стандартный, за исключением того, что у меня возникли проблемы с получением istream_iterator до stop Итерирование, когда оно попадает в конец потока. Прямо сейчас, он просто петли навсегда, хотя звонки в istream::tellg() указывают, что я в конце файла.

Вот код, чтобы воспроизвести проблему:

struct Foo 
{ 
    Foo() { }  
    Foo(char a_, char b_) : a(a_), b(b_) { } 

    char a; 
    char b; 
}; 

// Output stream operator 
std::ostream& operator << (std::ostream& os, const Foo& f) 
{ 
    os << f.a << f.b; 
    return os; 
} 

// Input stream operator 
std::istream& operator >> (std::istream& is, Foo& f) 
{ 
    if (is.good()) 
    { 
     std::istreambuf_iterator<char> it(is); 
     std::istreambuf_iterator<char> end; 

     if (it != end) { 
      f.a = *it++; 
      f.b = *it++; 
     } 
    } 
    return is; 
} 

int main() 
{ 
    { 
     std::ofstream ofs("foo.txt"); 
     ofs << Foo('a', 'b') << Foo('c', 'd'); 
    } 

    std::ifstream ifs("foo.txt"); 
    std::istream_iterator<Foo> it(ifs); 
    std::istream_iterator<Foo> end; 
    for (; it != end; ++it) cout << *it << endl; // iterates infinitely 
} 

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

Таким образом, проблема заключается в том, что хотя istreambuf_iterator достигает конца буфера потока, сам фактический поток не вводит состояние EOF. Вызов istream::eof() возвращает false, хотя istream::tellg() возвращает последний байт в файле, а istreambuf_iterator<char>(ifs) сравнивает true с istreambuf_iterator<char>(), что означает, что я определенно в конце потока.

Я посмотрел на код библиотеки IOstreams, чтобы увидеть, как именно это определение, может ли istream_iterator находится в конечной позиции, и в основном он проверяет istream::operator void*() const вычисляет true. Эта функция библиотеки IStream просто возвращает:

return this->fail() ? 0 : const_cast<basic_ios*>(this); 

Другими словами, она возвращает 0 (ложь), если failbit установлен. Затем он сравнивает это значение с тем же значением в сконфигурированном по умолчанию экземпляре istream_iterator, чтобы определить, находимся ли мы в конце.

Итак, я попытался вручную установить failbit в моей подпрограмме std::istream& operator >> (std::istream& is, Foo& f), когда istreambuf_iterator сравнивает true с итератором конца. Это отлично работало и правильно завершило цикл. Но теперь я действительно смущен. Кажется, что istream_iteratorопределенно проверяет на наличие std::ios::failbit, чтобы обозначить состояние «конца потока». Но разве это не то, что для std::ios::eofbit? Я думал, что failbit был для условий ошибки, например, если базовый файл fstream не мог быть открыт или что-то в этом роде.

Итак, зачем мне звонить istream::setstate(std::ios::failbit), чтобы получить цикл для завершения?

+0

looping forever указывает на то, что поток испортился. Вопрос в том, почему? –

+0

@Martin, ну, даже если я заменю поток файлов на 'std :: stringstream', возникает та же проблема. Таким образом, это не может быть проблемой низкоуровневого файла. – Channel72

+0

Рединг @ Ответ PigBen. Причина в том, что на внешнем уровне вы используете istream_iterator (в for_each) и istreambuf_iterator внутри (operatro >>). Вы должны быть последовательными в своем использовании. ИСПОЛЬЗУЙТЕ istreambuf_iterators в обеих ситуациях, и он должен работать. –

ответ

5

Когда вы используете istreambuf_iterator, вы управляете базовым объектом streambuf объекта istream. Объект streambuf ничего не знает о его владельце (объект istream), поэтому вызывающие функции объекта streambuf не вносят изменений в объект istream. Вот почему флаги в объекте istream не установлены, когда вы достигаете eof.

ли что-то вроде этого:

std::istream& operator >> (std::istream& is, Foo& f) 
{ 
    is.read(&f.a, sizeof(f.a)); 
    is.read(&f.b, sizeof(f.b)); 
    return is; 
} 

Редактировать

Я шагал через код в моем отладчика, и это то, что я нашел. istream_iterator имеет два внутренних элемента данных. Указатель на связанный объект istream и объект типа шаблона (в этом случае Foo). При вызове ++, он вызывает эту функцию:

void _Getval() 
{ // get a _Ty value if possible 
    if (_Myistr != 0 && !(*_Myistr >> _Myval)) 
     _Myistr = 0; 
} 

_Myistr является указателем IStream и _Myval является объектом Foo. Если вы смотрите здесь:

!(*_Myistr >> _Myval) 

Это то, где он называет вашего оператора >> перегрузкой. И он вызывает оператора! на возвращаемом объекте istream. И как вы можете видеть here, оператор! возвращает true только в том случае, если установлен битбит или битбит, eofbit этого не делает.

Итак, что произойдет дальше, если установлены либо битбиты, либо битбиты, указатель istream получает значение NULL'd. И в следующий раз, когда вы сравниваете итератор с конечным итератором, он сравнивает указатель istream, который является NULL для обоих из них.

+0

Я бы предпочел использовать istreambuf_iterator, потому что он позволяет мне повторно использовать одни и те же подпрограммы в общих чертах с другими типами итераторов. (Например, я могу десериализовать свой объект, когда он хранится в 'std :: string', используя' string :: iterator'.) Но я понимаю, что вы говорите - два набора итераторов не сообщаются. Итак, почему это не работает, если я просто вручную вызываю 'istream :: setstate (std :: ios :: eofbit)' на объект 'istream', когда' istreambuf_iterator' достигает конца? – Channel72

+0

Я бы предположил, что это связано с тем, что когда вы сравниваете итератор с итератором конца потока, он проверяет failbit, а не eofbit. Имеет смысл сделать это, потому что при нормальной работе объекта istream (с использованием оператора >>), всякий раз, когда устанавливается eofbit, устанавливается также и failbit. Однако обратное не всегда верно, поэтому имеет смысл проверить failbit. В вашей функции, однако, вы должны подражать поведению оператора >> и установить оба. –

0

Я думаю, что ваше конечное условие должно использовать метод .equal(), вместо использования оператора сравнения.

for (; !it.equal(end); ++it) cout << *it << endl; 

Я обычно вижу это реализуется с помощью цикла While вместо для цикла:

while (!it.equal(end)) { 
    cout << *it++ << endl; 
} 

Я думаю, что эти два будет иметь тот же эффект, но (для меня) цикл в то время как понятнее.

Примечание. У вас есть ряд других мест, в которых вы используете оператор сравнения, чтобы проверить, есть ли итератор на уровне eof. Вероятно, все это должно быть переключено на использование .equal().

1

Похоже, что два набора потоковых итераторов являются interfearing друг с другом:

Я получил это работает с этим:

// Input stream operator 
std::istream& operator >> (std::istream& is, Foo& f) 
{ 
    f.a = is.get(); 
    f.b = is.get(); 

    return is; 
} 
+0

в порядке - см. Мои комментарии к PigBen. Я бы предпочел использовать 'std :: istreambuf_iterator', потому что использование итераторов позволяет мне писать общие подпрограммы, которые могут работать на любом контейнере, а не на подпрограммах, которые работают только с потоками. – Channel72

2

Ваш внешний контур — где вы проверяете для вашего istream_iterator в достигли своего конца — привязан к состоянию, хранящемуся в унаследованном istreamios_base. Состояние на istream представляет собой результат недавних операций извлечения, выполненных против самого istream, а не состояния его базового streambuf.

Ваш внутренний цикл — где вы используете istreambuf_iterator для извлечения символов из streambuf — использует низкоуровневые функции, как basic_streambuf::sgetc() (для operator*) и basic_streambuf::sbumpc() (для operator++). Ни одна из этих функций не устанавливает флаги состояния как побочный эффект, кроме второго, который продвигается basic_streambuf::gptr.

Ваша внутренняя петля работает нормально, но она реализована в скрытом способе, упакованном как есть, и это нарушает the contract of std::basic_istream& operator>>(std::basic_istream&, T&). Если функция не может извлечь элемент по назначению, он должен вызывать basic_ios::setstate(badbit) и, если он также встречается с окончанием потока при извлечении, он также должен вызвать basic_ios::setstate(eofbit). Функция экстрактора не устанавливает ни одного флага, когда не удается извлечь Foo.

Я согласен с другим советом здесь, чтобы избежать использования istreambuf_iterator для реализации оператора извлечения, предназначенного для работы на уровне istream. Вы заставляете себя выполнять дополнительную работу, чтобы поддерживать контракт istream, что приведет к другим неожиданным сюрпризам, таким как тот, который привел вас сюда.

1

В вашем operator>> вы должны установить failbit в любое время, когда вы не смогли успешно прочитать Foo. Кроме того, вы должны установить eofbit в любое время, когда вы обнаруживаете конец файла. Это может выглядеть следующим образом:

// Input stream operator 
std::istream& operator >> (std::istream& is, Foo& f) 
{ 
    if (is.good()) 
    { 
     std::istreambuf_iterator<char> it(is); 
     std::istreambuf_iterator<char> end; 

     std::ios_base::iostate err = it == end ? (std::ios_base::eofbit | 
                std::ios_base::failbit) : 
               std::ios_base::goodbit; 
     if (err == std::ios_base::goodbit) { 
      char a = *it; 
      if (++it != end) 
      { 
       char b = *it; 
       if (++it == end) 
        err = std::ios_base::eofbit; 
       f.a = a; 
       f.b = b; 
      } 
      else 
       err = std::ios_base::eofbit | std::ios_base::failbit; 
     } 
     if (err) 
      is.setstate(err); 
    } 
    else 
     is.setstate(std::ios_base::failbit); 
    return is; 
} 

С помощью этого экстрактора, который устанавливает failbit на неспособность читать и eofbit при обнаружении EOF файла, драйвер работает, как ожидалось. Обратите внимание, что даже если ваш внешний if (is.good()) не работает, вам все равно необходимо установить failbit. Ваш поток может быть !good(), потому что установлен только eofbit.

Вы можете слегка упростить приведенное выше, используя внешний интерфейс для измерения istream::sentry. Если sentry не удается, он будет установлен failbit для вас:

// Input stream operator 
std::istream& operator >> (std::istream& is, Foo& f) 
{ 
    std::istream::sentry ok(is); 
    if (ok) 
    { 
     std::istreambuf_iterator<char> it(is); 
     std::istreambuf_iterator<char> end; 

     std::ios_base::iostate err = it == end ? (std::ios_base::eofbit | 
                std::ios_base::failbit) : 
               std::ios_base::goodbit; 
     if (err == std::ios_base::goodbit) { 
      char a = *it; 
      if (++it != end) 
      { 
       char b = *it; 
       if (++it == end) 
        err = std::ios_base::eofbit; 
       f.a = a; 
       f.b = b; 
      } 
      else 
       err = std::ios_base::eofbit | std::ios_base::failbit; 
     } 
     if (err) 
      is.setstate(err); 
    } 
    return is; 
} 

sentry также пропускает ведущие пробелы. Это может быть или не быть тем, что вы хотите. Если вы не хотите, чтобы караул пропустить ведущие пробельные вы можете построить его с:

std::istream::sentry ok(is, true); 

Если sentry обнаруживает конец файла, пропуская ведущие пробелы, он будет установлен как failbit и eofbit.