понедельник, 7 декабря 2009 г.

[comp.prog.c++] std::istream, Poco::Base64Decode и std::ios::failbit

Никогда не был любителем C++ных потоков ввода-вывода (это которые std::istream, std::ostream и иже с ними). Дизайн у них какой-то… выхереный, как говорили в нашем дворе когда-то. Пытался по молодости разобраться, как писать свои stream-ы, плюнул, старался их меньше использовать. Собственно, я понимаю, что не люблю кошек потому, что не умею их готовить. Но все-таки. Вот на днях в очередной раз наткнулся на вещь с потоками, которую сложно понять умом. Как хорошо было сказало в старом анекдоте: “Запомните это дети, поскольку понять это невозможно”.

Итак, суть в том, что нужно преобразовать текст из Base64 в нормальный вид. С использованием класса Poco::Base64Decoder это идиоматически выглядит так:

std::string base64decode( const std::string & what )
  {
    std::istringstream instream( what );
    Poco::Base64Decoder decoder( instream );

    std::string result;
    Poco::StreamCopier::copyToString( decoder, result );

    return result;
  }

Но у этого кода есть существенный недостаток – не порождается исключений, если в what находится не Base64 строка. Чтобы исключения порождались, нужно воспользоваться методом std::basic_ios::exceptions:

Poco::Base64Decoder decoder( instream );
decoder.exceptions( std::ios::badbit | std::ios::failbit );

Теперь, казалось бы, все должно быть нормально – если на вход подается не Base64, то порождается исключение (срабатывает битовый флаг badbit).

Но есть засада! Если на вход подать пустую строку, то исключение так же выскочит. Но уже благодаря битовому флагу failbit (по крайней мере это так в Visual C++ 2003/2008 и в MinGW 3.4.5).

Самое внятное описание роли флагов badbit и failbit мне удалось найти в руководстве по стандартной библиотеке C++ от Apache. Там приводится пример двух ситуаций, когда при достижении конца потока в одном случае выставляется failbit, а в другом случае – нет. И, вероятно, один из подобных случаев я поимел, когда подсунул Base64Decoder пустую строку (аналогичная ситуация возникает и без Base64Decoder, когда из пустого потока пытаешься прочитать строку).

Там же в Apache-вском руководстве дан интересный совет: устанавливать с помощью exceptions() только badbit, но не устанавливать eofbit и failbit. Мол, ситуации, которые провоцируют установку eofbit и failbit, обычно не являются исключительными.

Однако, моя параноидальная натура сопротивляется такому совету. Ведь если failbit может быть установлен независимо от eofbit, то хотелось бы эту ситуацию обработать. Ошибка же, как никак. Посему, корректным решением мне видится такое использование Base64Decoder:

std::string base64decode( const std::string & what )
  {
    std::istringstream instream( what );
    Poco::Base64Decoder decoder( instream );
    decoder.exceptions( std::ios::badbit );

    std::string result;
    Poco::StreamCopier::copyToString( decoder, result );
    if( !decoder.eof() && decoder.fail() )
      throw Poco::RuntimeException( "Some error reading Base64 stream" );

    return result;
  }

Что, с моей точки зрения, не есть красиво :(

Мораль сей басни: разработчики Poco – редиски. Нет чтобы сделать выбрасывание исключений в Base64Decoder при обнаружении не-Base64 символа вне зависимости от std::stream-овских флагов исключений! (Шутка).

Комментариев нет: