HOWTO: Свій потік вводу-виводу мови С + + (исходники), Різне, Програмування, статті

Встань біля річки, дивися, як тече річка; Її не зловити ні в мережу, ні рукою. Вона безіменна, адже ім’я є лише у її берегів; Забудь своє ім’я і стань рікою.
Борис Гребенщиков

Бібліотека введення-виведення мови С + + – досить спірне явище. Але, так чи інакше, вона існує, іноді використовується, і треба якось з цим жити.


Типові сценарії роботи з потоком – породження і перетворення. Породження – це, наприклад, видача в потік даних з сокета. Хороший приклад перетворення – перекодування (у base64, в іншу кодування, шифрування, архівування). Ще можна щось зайве видаляти (прогалини, коментарі), а щось потрібне додавати (розгортати макроси). Але при найближчому розгляді виявляється, що перетворення – Це окремий випадок породження, коли дані “породжуються” не “з сокета”, а на основі вихідного потоку. І, в результаті, все зводиться до створення потоку зі стандартним інтерфейсом і своїм власним джерелом даних.


Постановка завдання проста і логічна, розробники бібліотеки iostream, звичайно, про неї здогадувалися і навіть зробили деякі кроки … Потрібно тільки зрозуміти, які. Отже, завдання на reverse engineering: є купа коду (реалізація бібліотеки), потрібно зрозуміти, як він працює і що хотіли сказати автори.


Власне, все, що вони хотіли сказати, вони сказали самою бібліотекою, але в ній занадто багато сюжетних ліній, які перетинаються дивним чином. Зрозуміти її цілком може тільки компілятор, а нам потрібен один маленький шматочок …


Шукати сенс будемо в кілька ідеалізованих исходниках з VC2005, шукати чесно: майже половину статті становить скопійований код iostream 🙂 Головне – викинути зайве і розставити залишився в потрібному порядку.






ПРИМІТКА

Кінцевий результат перевірявся в VC2005 і в gcc 4.2. STLPort відрізняється неістотно, а публічні і захищені методи взагалі прописані в стандарті і повинні збігатися у всіх реалізаціях.


Ідеалізація – видалені деякі незначні дрібниці …


Звичайно, я вже знаю “правильну відповідь”, і міг би пояснити його як-небудь коротше, зрозуміліше і систематично … Але мені здається більш цікавим провести вас тим же шляхом, яким ішов я сам, по сторонам відкриваються приголомшливі види. Слідуйте за мною.


Всі круги istream


 І я тобі скажу в свою череду: Іди за мною, і в вічні сільця З цих місць тебе я приведу, І ти почуєш крики нестями І стародавніх парфумів, бідуючих там, Про нову смерті марні моленья;
Данте Аліг’єрі, переклад Михайла Лозинського

Почнемо вирішувати завдання з вивчення обстановки.


Генеалогія


По-перше, istream це взагалі не клас, а всього лише typedef.





typedef basic_istream<char, char_traits<char> > istream;

Реальний клас називається basic_istream:





template<class _Elem, class _Traits>
class basic_istream: virtual public basic_ios<_Elem, _Traits>
{ // control extractions from a stream buffer

Він успадкований від:





template<class _Elem, class _Traits>
class basic_ios: public ios_base
{ // base class for basic_istream/basic_ostream

А той від:





class ios_base : public _Iosb<int>
{ // base class for ios

Ну і нарешті:





template<class _Dummy>
class _Iosb
{ // define templatized bitmask/enumerated types, instantiate on demand





ПРИМІТКА

Клас _Iosb – Microsoft specific, така особливість реалізації, ні на що істотне не впливає. Решта прописані в стандарті.


Узагальнені символи


У відповідності з ідеологією STL, потік працює з узагальненими символами. Конкретизація потоку отримує в параметрах шаблону клас символів і пачку методів для роботи з ними. “Пачка методів” виглядає приблизно так:





template<class _Elem>
struct char_traits
{
typedef _Elem char_type;
typedef long int_type;
typedef streampos pos_type;
typedef streamoff off_type;
typedef _Mbstatet state_type;
static void assign(_Elem& _Left, const _Elem& _Right);
static bool eq(const _Elem& _Left, const _Elem& _Right);
static bool lt(const _Elem& _Left, const _Elem& _Right);
static int compare(const _Elem *_First1, const _Elem *_First2, size_t _Count);
static size_t length(const _Elem *_First);
static _Elem* copy(_Elem *_First1, const _Elem *_First2, size_t _Count);
static const _Elem* find(const _Elem *_First, size_t _Count, const _Elem& _Ch);
static _Elem* move(_Elem *_First1, const _Elem *_First2, size_t _Count);
static _Elem* assign(_Elem *_First, size_t _Count, _Elem _Ch);
static _Elem to_char_type(const int_type& _Meta);
static int_type to_int_type(const _Elem& _Ch);
static bool eq_int_type(const int_type& _Left, const int_type& _Right);
static int_type eof();
static int_type not_eof(const int_type& _Meta);
};

І замість стандартних функцій і операторів ==,>, <реалізація потоку чесно використовує саме ці методи. Трохи довше і виглядає дивно, зате однаково успішно працює з char і з wchar_t (для них зроблені явні спеціалізації char_traits). Ще можна робити ось такі непотрібні штуки:





#include <fstream>
namespace std
{ / / Якщо не визначити свій char_traits, все / / Буде перетворюватися до int-у, дробові частини загубляться
template<> struct char_traits<float>
{
typedef float _Elem;
typedef float char_type;
typedef float int_type;
… / / Сюди скопіювати реалізацію char_traits
};
typedef basic_fstream<float, std::char_traits<float> > ffstream;
}
int main()
{
std::ffstream fs(“test”, std::ios_base::out);
float f[10] = {1.1, 2.2, 3.3, -1, 5.5, 6.6, 7.7, 8.8, 9.9, 0}; fs.write (f, 10); / / Ну, це зрозуміло fs << "test"; / / Але і це теж працює! fs << 3.1416; / / Вгадайте, як працює ось це? fs << 3.14f; / / А якщо так? fs.close(); }





ПРИМІТКА

Це код для VC2005, і він більш-менш робітник.


В gcc char_traits реалізований більш гнучко: використовувані типи задаються структурою _Char_types, яку можна явно спеціалізувати для float. Цей код компілюється і запускається, але чомусь зовсім не працює, не розбирався чому.


Невелика проблема полягає в існуванні char_traits :: eof (). Значення, що є ознакою кінця, не має належати char_type, інакше воно може несподівано зустрітися в середині файлу. Саме для цього введено тип int_type: він повинен включати в себе весь char_type і ще хоча б одне значення, яке можна буде оголосити eof-ом.


У спеціалізації char_traits для char зроблено так:





typedef char _Elem;
typedef _Elem char_type;
typedef int int_type;

static int_type to_int_type(const _Elem& _Ch)
{ // convert character to metacharacter
return ((unsigned char)_Ch);
}
static int_type eof()
{ // return end-of-file metacharacter return (EOF); ​​/ / визначений як -1 – С.Х.
}

В результаті eof це -1, а нормальні символи завжди перетворюються до позитивних int-ам. Наведена реалізація спеціалізації char_traits для float всього цього не враховує, через що потік не зовсім коректно реагує на -1 на вході.


Джерело даних


Через деякий час стає ясно, що basic_istream зовсім не абстрактний базовий клас, в якому можна перевизначити чисто віртуальний метод get, що зчитує наступний символ. Взагалі, у всій ієрархії класів віртуальні – тільки деструктори, інших віртуальних методів не спостерігається.


Зате є дещо не віртуальних get-ов, найпростіший з них виглядає так:





// extract a metacharacter
int_type get()
{
int_type _Meta = 0;
ios_base::iostate _State = ios_base::goodbit;
_Chcount = 0;
const sentry _Ok(*this, true);
if (!_Ok)
_Meta = _Traits::eof(); // state not okay, return EOF
else
{
// state okay, extract a character
_TRY_IO_BEGIN _Meta = _Myios :: Rdbuf () -> sbumpc (); <- Дивитися сюди! if (_Traits::eq_int_type(_Traits::eof(), _Meta)) _State /= ios_base::eofbit / ios_base::failbit; // end of file else ++_Chcount; // got a character, count it _CATCH_IO_END } _Myios::setstate(_State); return (_Meta); }

Метод rdbuf визначено в класі basic_ios:





template<class _Elem, class _Traits>
class basic_ios : public ios_base
{
public:
typedef basic_streambuf<_Elem, _Traits> _Mysb;

_Mysb* rdbuf() const
{
// return stream buffer pointer
return (_Mystrbuf);
}
private:

_Mysb *_Mystrbuf; // pointer to stream buffer
};

Для того щоб остаточно зацікавитися класом basic_streambuf залишилося привести код основного конструктора класу basic_istream:





// construct from stream buffer pointer
explicit basic_istream(_Mysb *_Strbuf, bool _Isstd = false) : _Chcount(0)
{
_Myios::init(_Strbuf, _Isstd);
}





ПРИМІТКА

Чому “основного”? Реалізація basic_istream в VC2005 містить ще один конструктор:


basic_istream(_Uninitialized)


{


ios_base::_Addstd(this);


}


Судячи за викликом _Addstd, його хотіли використовувати для стандартних потоків cin / cout / cerr, проте використовувати все ж таки не стали (див. файл cout.cpp в исходниках crt з VC2005). Булева параметр _Isstd, мабуть, теж призначався для стандартних потоків, але теж не використовується. В STLPort немає ні другого конструктора, ні другого параметра, в стандарті теж.


Джерело даних-II


Оптиміст почав би розмотувати basic_streambuf з того, на чому зупинилися, тобто з sbumpc. Але це занадто ненадійний шлях. Замість цього ще трохи подивимося, звідки basic_istream бере дані.






ПРИМІТКА

Для ясності і стислості з коду прибрані перевірки стану, параметрів і ще деякі дрібниці. Якщо вам воно треба – зверніться до першоджерела.


Більш цікавий метод get:





// get up to _Count characters into NTCS, stop before _Delim
_Myt& get(_Elem *_Str, streamsize _Count, _Elem _Delim)
{
ios_base::iostate _State = ios_base::goodbit;
_Chcount = 0;
// extract characters
int_type _Meta = _Myios::rdbuf()->sgetc(); <– (1)
for (; 0 < –_Count; _Meta = _Myios::rdbuf()->snextc()) <– (2)
if (_Traits::eq_int_type(_Traits::eof(), _Meta))
{ // end of file, quit
_State /= ios_base::eofbit;
break;
}
else if (_Traits::to_char_type(_Meta) == _Delim)
break; // got a delimiter, quit
else
{ // got a character, add it to string
*_Str++ = _Traits::to_char_type(_Meta);
++_Chcount;
}
_Myios::setstate(_Chcount == 0 ? _State / ios_base::failbit : _State);
*_Str = _Elem(); // add terminating null character
return (*this);
}





ПРИМІТКА

Зверніть увагу на значення, що повертається: це посилання на себе. Щоб отримати кількість прочитаних символів, потрібно викликати метод gcount.


Метод read:





_Myt& read(_Elem *_Str, streamsize _Count)
{
return _Read_s(_Str, (size_t)-1, _Count);
}

_Read_s Придумана програмістами з Microsoft і реалізована так:





// read up to _Count characters into buffer
_Myt& _Read_s(_Elem *_Str, size_t _Str_size, streamsize _Count)
{
ios_base::iostate _State = ios_base::goodbit; _Chcount = 0; / – ось тут
V
const streamsize _Num = _Myios::rdbuf()->_Sgetn_s(_Str, _Str_size, _Count);
_Chcount += _Num;
if (_Num != _Count)
_State /= ios_base::eofbit / ios_base::failbit; // short read
_Myios::setstate(_State);
return (*this);
}

Суфікс “_s” утворений від слова “secure”. Мається на увазі, що, раз їй передається на один розмір більше, вона більш безпечна, деякі в це вірять. _Sgetn_s – Це теж ідея Microsoft, у стандартній реалізації basic_streambuf її немає. Але це ми забігаємо вперед.






ПРИМІТКА

Я не маю на увазі, що всі _s-функції марні, я все не дивився. Але в даному випадку … Функція приймає на вхід два числа: розмір буфера і число зчитувальних символів. Як ви думаєте, що вона робить? Правильно, вибирає менше з двох. Не бачу, чому цього не може зробити викликає код. Не розумію, на що він взагалі може розраховувати, намагаючись прочитати більше, ніж влазить.


Метод рeek:





// return next character, unconsumed
int_type peek()
{
ios_base::iostate _State = ios_base::goodbit;
_Chcount = 0;
int_type _Meta = 0;
if (_Traits::eq_int_type(_Traits::eof(), _Meta = _Myios :: Rdbuf () -> sgetc ())) <- Ага! _State /= ios_base::eofbit; _Myios::setstate(_State); return (_Meta); }

Схожим чином влаштовані putback, unget, sync, seekg, і tellg: вони просто передають управління відповідним їм sputbackc, sungetc, pubsync, pubseekpos, pubseekoff.


Один з вбудованих операторів >>:





typedef istreambuf_iterator<_Elem, _Traits> _Iter;
typedef num_get<_Elem, _Iter> _Nget;

// extract an int
_Myt& operator>>(int& _Val)
{
ios_base::iostate _State = ios_base::goodbit;
long _Tmp = 0;
const _Nget& _Nget_fac = _USE(ios_base::getloc(), _Nget);
_Nget_fac.get(_Iter(_Myios::rdbuf()), _Iter(0), *this, _State, _Tmp);
if (_State & ios_base::failbit // _Tmp < INT_MIN // INT_MAX < _Tmp)
_State /= ios_base::failbit;
else
_Val = _Tmp;
_Myios::setstate(_State);
return (*this);
}

Ми не будемо заглиблюватися в реалізацію istreambuf_iterator, тим більше що він використовує вже зустрічалися sbumpc і sgetc. А num_get, в реалізацію якого ми теж заглиблюватися не будемо, просто використовує ітератор.


Ну і вистачить. Що у нас вийшло:



Можливо, щось ми пропустили, але деяке розуміння того, як працює basic_streambuf вже повинно скластися.


По болотах basic_streambuf


 – Давайте відріжемо Сусанину ногу. – Не треба, хлопці, я згадав дорогу!
фольклор

На жаль, ідея, закладена в клас basic_streambuf, не виводиться очевидним чином з його реалізації, тому тут мені не вдасться обмежитися копіюванням до статті вихідного коду; доведеться і самому щось написати.


Пропонована модель функціонування спадкоємця basic_streambuf:







ПРИМІТКА

За GoF, використаний патерн проектування називається Template Method; його ж часто називають Non-Virtual Interface.


Спасибо Андрію Солодовникова за нагадування назви патерну і кілька інших важливих поправок.


Для простоти буферизацію можна відключити, а більшу частину “інших ситуацій” ігнорувати. Якщо як буфера встановити 0, всі операції будуть “виходити за край”, тобто кожен раз будуть викликатися обробники; залишилося тільки правильно їх реалізувати. Цим і займемося, до буферам і до решти повернемося пізніше.


Просте читання, методи


Спочатку – публічний інтерфейс. Викинемо typedef-и, роботу з буферами, позиціонування, локалізації і все інше, що нас зараз не цікавить:





// control read/write buffers
template<class _Elem, class _Traits>
class basic_streambuf
{
public:

// Get area:
int_type sgetc();
int_type sbumpc();
int_type snextc();
streamsize sgetn(_Elem *_Ptr, streamsize _Count);
// MS specific
streamsize _Sgetn_s(_Elem *_Ptr, size_t _Ptr_size, streamsize _Count);

};





ПРИМІТКА

Заодно ми викинули putback, до нього теж повернемося пізніше.


Реалізація putback без буферів – непотрібний ручна праця, що загрожує помилками; при правильному використанні буферів все виходить само.


sgetc


Повертає поточний символ і залишає покажчик на місці. Виклик sgetc в циклі повинен весь час повертати одне і те ж.





int_type sgetc()
{ // get a character and don”t point past it
return (0 < _Gnavail() ? _Traits::to_int_type(*gptr()) : underflow());
}

На краю буфера викликається underflow:





virtual int_type underflow();

Це перший перевизначають метод.


sbumpc


Повертає поточний символ і пересуває покажчик вперед. Виклик sbumpc в циклі повинен послідовно прочитати всі доступні символи.





int_type sbumpc()
{ // get a character and point past it
return (0 < _Gnavail() ? _Traits::to_int_type(*_Gninc()) : uflow());
}

Якщо буфер скінчився, викликається unflow:





virtual int_type uflow();

Це другий перевизначають метод.


snextc


Пересуває покажчик вперед і повертає новий поточний символ. Виклик snextc в циклі повинен послідовно прочитати всі доступні символи крім першого. Призначений для використання в парі з sgetc.





int_type snextc()
{ // point to next character and return it
return (1 < _Gnavail()
? _Traits::to_int_type(*_Gnpreinc())
: _Traits::eq_int_type(_Traits::eof(), sbumpc())
? _Traits::eof() : sgetc());
}

Логіка роботи:



Ніяких обробників прямо не викликається, досить коректно реалізувати sbumpc і sgetc.


sgetn, _Sgetn_s


Намагаються скопіювати за переданим адресою задану кількість символів, повертають кількість реально скопійованих.





streamsize sgetn(_Elem *_Ptr, streamsize _Count)
{ // get up to _Count characters into array beginning at _Ptr
return xsgetn(_Ptr, _Count);
}
streamsize _Sgetn_s(_Elem *_Ptr, size_t _Ptr_size, streamsize _Count)
{ // get up to _Count characters into array beginning at _Ptr
return _Xsgetn_s(_Ptr, _Ptr_size, _Count);
}

Викликають xsgetn і _Xsgetn_s відповідно:





virtual streamsize xsgetn(_Elem* _Ptr, streamsize _Count)
{ // get _Count characters from stream
// assume the destination buffer is large enough
return _Xsgetn_s(_Ptr, (size_t)-1, _Count);
}
virtual streamsize _Xsgetn_s(_Elem* _Ptr, size_t _Ptr_size, streamsize _Count)
{

}

Для коректної роботи стандартних реалізацій досить sbumpc, нічого нового вони не викликають. Перевизначати має сенс, тільки якщо можна збільшити ефективність, ми не будемо цим займатися.






РАДА

Зверніть увагу: реалізація basic_istream від Microsoft не викликає sgetn взагалі, а значить, і ваша ефективна xsgetn практично не принесе користі. Доведеться перевизначити _Xsgetn_s, спеціально для MS.


Просте читання, обробники


Виявлено:



Приблизно на десять хвилин роботи. Ось такий корисний допоміжний клас:





template <typename ch, typename tr>
class basic_symbol_istreambuf: public std::basic_streambuf<ch, tr>
{
public:
typedef std::basic_streambuf<ch, tr> base; / / Інакше їх не бачить gcc
typedef typename base::int_type int_type;
typedef typename base::traits_type traits_type;
basic_symbol_istreambuf() : _ready(false)
{ / / Відмовляємося від буферів
setg(0, 0, 0);
setp(0, 0);
}
protected:
/ / Інакше їх не бачить gcc
using base::setg;
using base::setp;
/ / Визначить нащадок / / Повертає або traits_type :: eof (), або traits_type :: to_int_type (c)
virtual int_type readChar() = 0;
/ / Реалізація underflow
virtual int_type underflow()
{
if (_ready)
{ / / Поточний символ вже прочитаний
return _char;
}
_char = readChar();
_ready = true;
return _char;
}
/ / Реалізація uflow
virtual int_type uflow()
{
if (_ready)
{ / / Поточний символ вже прочитаний
if (_char != traits_type::eof())
{ / / І він не останній – потрібно “перейти до наступного”
_ready = false;
}
return _char;
}
/ / Поточний символ ще не прочитаний
return readChar();
}
private:
int_type _char;
bool _ready;
};

А тепер:





class random_buf
: public basic_symbol_istreambuf<char, std::char_traits<char> >
{
public:
random_buf()
{
srand(time(0));
}
int readChar()
{
return traits_type::to_int_type(rand());
}
};
int main()
{
random_buf rb;
std::istream st(&rb);
std::string c; st >> c; / / Читає до першого пробельного символу
std::cout << c << ”
“;
}

Вуаля! Створення потоку виглядає трохи незграбно, але програма працює.


Через буфери до зірок


 With our full crew a-board
And our trust in the Lord
We”re comin” in on a wing and a prayer

Harold Adamson


Для роботи з буфером даних-для-читання нам доступні наступні функції:





protected: void setg (_Elem * _First, _Elem * _Next, _Elem * _Last); / / Ініціалізація
_Elem * Eback () const; / / Початок буфера _Elem * Gptr () const; / / Поточний символ _Elem * Egptr () const; / / Кінець void gbump (int _Off); / / Зсуває позицію поточного символу






Імена функцій – це головоломка:


* “G” в setg, gptr, egptr і gbump – від “get”


* “E” в eback і egptr – від “end”


Ідея в тому, що у буфера є “поточне становище” і два способи пересування: вперед і назад. Пересування будь-яким із способів небудь впирається в край буфера, відповідно, у буфера є два кінці: кінець для руху вперед і кінець для руху назад; egptr і eback. “Початок” – в поточній точці, тут-і-тепер.


Логіка освіти імен цих функцій була відкрита мені Миколою Меркин, Спасибі йому.


Можна вважати, що setg присвоює значення вказівниками, значення яких повертають eback, gptr і egptr. Що ще потрібно відзначити:



Хороші новини


Загалом-то, з буфером навіть простіше. Так, потрібно запам’ятати ще кілька функцій, але зате, як з’ясовується, вони внесені в інтерфейс basic_streambuf не випадково, він був розрахований саме на таке використання.


Наприклад, обробник uflow має стандартну реалізацію, ось вона:





virtual int_type uflow()
{ // get a character from stream, point past it
return (_Traits::eq_int_type(_Traits::eof(), underflow())
? _Traits::eof() : _Traits::to_int_type(*_Gninc()));
}

Логіка роботи:



Тут неявно мається на увазі, що якщо underflow щось успішно прочитала, то вона заносить це в буфер. І якщо це дійсно так, uflow можна не перевизначати, стандартна прекрасно працює.


Інший приклад – реалізація putback / unget. В basic_streambuf їм відповідають sputbackc і sungetc:





int_type sputbackc(_Elem _Ch)
{ // put back _Ch
return (gptr() != 0 && eback() < gptr() && _Traits::eq(_Ch, gptr()[-1])
? _Traits::to_int_type(*_Gndec())
: pbackfail(_Traits::to_int_type(_Ch)));
}
int_type sungetc()
{ // back up one position
return (gptr() != 0 && eback() < gptr()
? _Traits::to_int_type(*_Gndec()) : pbackfail());
}

Перевизначають обробник виходу за межі – pbackfail:





virtual int_type pbackfail(int_type c = traits_type::eof());

Але стандартна реалізація цілком справляється без нього до тих пір, поки:



Чи треба вирішувати користувачеві більше – питання філософське, якщо не треба, то й робити нічого не доведеться.


Код


З двох обов’язкових для перевизначення функцій залишилася одна – underflow. У класі basic_buffered_istreambuf її потрібно перевизначити так, щоб вона:



Приблизно так:





/ / Визначить нащадок / / Повертає або кількість прочитаного, або -1
virtual int readData(char_type* buffer, size_t length) = 0;
/ / Реалізація underflow
virtual int_type underflow()
{ / / Читаємо нову порцію
int symbols_read = readData(_buffer, buffer_size);
if (symbols_read <= 0)
{ / / Не цілком вдало
setg(_buffer, _buffer, _buffer);
return traits_type::eof();
}
/ / Вдало!
setg(_buffer, _buffer, _buffer + symbols_read);
/ / Повертаємо поточний символ, не зрушуючи покажчик
return traits_type::to_int_type(*egptr());
}

Недолік цього рішення в тому, що відразу після виклику такий underflow перестає працювати стандартна реалізація putback: нікуди “відступити”, поточний елемент знаходиться на самому початку буфера. Це неприємно, нехай ми й обмежуємо глибину putback, але хочеться, щоб він завжди більш-менш працював.


Модифікована версія (а заодно буфер засунули в вектор, по морально-етичних міркувань):





template <typename ch, typename tr>
class basic_buffered_istreambuf: public std::basic_streambuf<ch, tr>
{
public:
typedef std::basic_streambuf<ch, tr> base; / / Інакше їх не бачить gcc
typedef typename base::int_type int_type;
typedef typename base::traits_type traits_type;
typedef typename base::char_type char_type;
basic_buffered_istreambuf(size_t size = 512, size_t back = 10)
: buffer_size(size), back_size(back)
{
_buffer.resize(back_size + buffer_size);
setg(0, 0, 0);
setp(0, 0);
}
protected:
/ / Інакше їх не бачить gcc
using base::setg;
using base::setp;
using base::eback;
using base::gptr;
using base::egptr;
/ / Визначить нащадок / / Повертає або кількість прочитаного, або -1
virtual int readData(char_type* buffer, size_t length) = 0;
/ / Реалізація underflow
virtual int_type underflow()
{
size_t offset = 0;

if (eback() != egptr())
{ / / Забезпечуємо собі putback / / Глибина не більше, ніж: / / – Кількість прочитаних символів / / – Константа back_size
offset = std::min<size_t>(back_size, egptr() – eback());
memmove(&_buffer[0], eback() – offset, offset);
}
/ / Читаємо нову порцію
int symbols_read = readData(&_buffer[offset], buffer_size);
if (symbols_readed <= 0)
{ / / Не цілком вдало
base::setg(&_buffer[0], &_buffer[offset], &_buffer[offset]);
return traits_type::eof();
}
/ / Вдало!
base::setg(&_buffer[0],
&_buffer[offset],
&_buffer[offset + symbols_read]);
/ / Повертаємо поточний символ не зрушуючи покажчик
return traits_type::to_int_type(*gptr());
}
private:
std::vector<char_type> _buffer;
const size_t buffer_size;
const size_t back_size;
};


Використовувати так само, як версію без буфера.






РАДА

До речі, basic_symbol_istreambuf корисно переписати з використанням буфера, для цього треба зробити buffer_size рівним 1. З плюсів: не треба буде перевизначати uflow, безкоштовно запрацює putback / unget.


… Дівчата?


 Тому, тому що ми пілоти, Небо наш, небо наш рідний дім …
Соломон Фогельсон

Настав “потім”, в общем-то, стаття закінчена. Але деякі припущення дрібниці хочеться згадати, деякі перспективи – продемонструвати, коротко, в режимі “попереджений – озброєний”. Дівчата почекають ще п’ять хвилин.


basic_streambuf::in_avail


Це остання функція, що відноситься до читання, вона використовується в basic_istream :: readsome і повертає кількість символів, які можна прочитати без затримок.





streamsize in_avail()
{ // return count of buffered input characters
streamsize _Res = _Gnavail();
return (0 < _Res ? _Res : showmanyc());
}

Вона повертає різницю між egptr і eback; якщо буфер відключений або порожній, повертає результат виклику showmanyc.





virtual streamsize showmanyc();





ПРИМІТКА

З стандарту: The morphemes of showmanyc are “es-how-many-see”, not “show-manic”.


Ви вгадали, саме заради цього забавного коментаря опис showmanic включено до статті 🙂


Що може повертати showmanyc:



Реалізація за замовчуванням повертає 0 і чудово підходить для більшості застосувань.


Управління позицією


Стандартний потоковий інтерфейс передбачає послідовний доступ до даних, але, якщо потік здатний на більше, iostream дозволяє йому проявити себе. Для довільного доступу призначені методи pubseekoff і pubseekpos:





pos_type pubseekoff(off_type _Off, ios_base::seekdir _Way,
ios_base::openmode _Mode = ios_base::in / ios_base::out)
{ // change position by _Off, according to _Way, _Mode
return (seekoff(_Off, _Way, _Mode));
}
pos_type pubseekpos(pos_type _Pos,
ios_base::openmode _Mode = ios_base::in / ios_base::out)
{ // change position to _Pos, according to _Mode
return (seekpos(_Pos, _Mode));
}

Вони реалізовані через seekoff і seekpos:





virtual pos_type seekoff(off_type off, ios_base::seekdir way,
ios_base::openmode mode);
virtual pos_type seekpos(pos_type pos, ios_base::openmode mode);

Що цікавого можна сказати про всю цю конструкцію:



Запис


Майже все, що говорилося про читання, вірно і для запису. Ну, там, звичайно, інші імена методів, але в цілому те ж саме. Єдина істотна відмінність – потрібен аналог для функції flush. Для цього призначений метод pubsync, який викликає перевизначають метод sync:





virtual int sync();

Значення, що повертається: -1 при помилці, щось інше – при успіху.


Природно, basic_ostream викликає його зі свого методу flush.






ПРИМІТКА

А ось навіщо виклик pubsync / sync потрібен в basic_istream – дійсно незрозуміло 🙂


Винятки


У наш прогресивний час повідомляти про проблеми, повертаючи -1, – це просто-таки ретроградство. Було б дивно, якби бібліотека з такою кількістю шаблонів не підтримувала винятку. Звичайно, вона їх підтримує. Але не самим очевидним способом.


Модель така:



Ну, і головне:







ПРИМІТКА

За стандартом всі перераховані тут методи розташовані в класі basic_ios, в Microsoft перемістили їх у ios_base і трохи поміняли. Функціональність збережена.


Для стандартних потоків така недомовленість щодо типів виключень має суттєвий мінус. Жодна реалізація stl не ризикне генерувати якесь “своє” виключення з коду fstream або stringstream – Інакше розраховує на нього користувальницький код буде нестерпним, і вся “стандартність” бібліотеки вилетить в трубу. В результаті ніяких виразних повідомлень про помилки стандартні класи потоків запропонувати не можуть: виключення використовувати не виходить, а інших коштів не передбачено.


Зате, якщо ви пишете свій потік, стандарт дає зелене світло: кидайте будь-які винятки, повна свобода.


Продуктивність


Взагалі-то, не дуже. В усякому разі, в VS2005 форматований вивід в буфер за допомогою sprintf працює приблизно в три рази швидше, ніж stringstream. Заміна stringstream на власний клас потоку не дає видимого ефекту.


Boost it


Як було сказано на самому початку: “постановка завдання проста і логічна”, і це дійсно так, нічого нового я не придумав. Зокрема, розробникам бібліотеки boost схожі думки теж приходили в голову, і з них народилася Boost.Iostreams Library. Проста, зрозуміла, зручна, яка працює.


Якщо обставини непереборної сили не примушують вас створювати власний велосипед, використовуйте boost. А все, що було написано вище, можна забути або навіть не знати. Але краще – знати і приймати до відома.


Все!


Тепер дівчата 🙂


Але й про потоки теж не забувайте.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*