#include <iostream> #include <sstream> #include <string> using namespace std; class numfilterbuf : public streambuf { private: istream *in; ostream *out; int cur; // , underflow() protected: /* : */ virtual int overflow(int c) override { if (c == traits_type::eof()){ return traits_type::eof(); } char_type ch = static_cast<char_type>(c); if (ch == ' ' || (ch >= '0' && ch <= '9')){ // out->put(ch); // - , EOF return out->good() ? ch : traits_type::eof(); } return ch; } /* : */ // - segmentation fault virtual int uflow() override { int c = underflow(); cur = traits_type::eof(); // underflow() return c; } virtual int underflow() override { if (cur != traits_type::eof()){ return cur; } // , while (in->good()){ cur = in->get(); if (cur == traits_type::eof()){ return traits_type::eof(); } char_type ch = static_cast<char_type>(cur); if (ch == ' ' || (ch >= '0' && ch <= '9')){ // return ch; } } return traits_type::eof(); } public: numfilterbuf(istream &_in, ostream &_out) : in(&_in), out(&_out), cur(traits_type::eof()) {} }; int main(int argc, char **argv){ const char str1[] = "In 4 bytes contains 32 bits"; const char str2[] = "Unix time starts from Jan 1, 1970"; istringstream str(str1); numfilterbuf buf(str, cout); // stringstream, iostream numfilter(&buf); // iostream string val; getline(numfilter, val); numfilter.clear(); // EOF stringstream cout << "Original: '" << str1 << "'" << endl; cout << "Read from numfilter: '" << val << "'" << endl; cout << "Original: '" << str2 << "'" << endl; cout << "Written to numfilter: '"; numfilter << str2; cout << "'" << endl; return 0; }
Original: 'In 4 bytes contains 32 bits' Read from numfilter: ' 4 32 ' Original: 'Unix time starts from Jan 1, 1970' Written to numfilter: ' 1 1970'
char_type
type. It is defined in the streambuf class and in our case it is an alias to the char
type, i.e. single byte character. More on this will be discussed at the end of the article.gptr
reaches it, it means that the buffer is exhausted and needs to be filled again.gptr
pointer to the offset
positions. In fact, after executing the function, gptr
will take the value gptr + offset
setp
only two arguments, unlike setg
. When initializing the output buffer pptr
automatically equated to pbase
(i.e. set at the beginning of the buffer)pptr
pointer to the offset
positions. In fact, after executing the function pptr
will take the value pptr + offset
streambuf
. It seemed to me that this class rather simply and clearly shows a simple operation with an output buffer. Therefore, in the following example, we will divide the output into parts and frame each with the <start>
and <end>
tags: #include <iostream> #include <sstream> #include <string> #include <vector> using namespace std; class blockoutputbuf : public streambuf { private: ostream *out; vector<char_type> buffer; string startb, endb; protected: virtual int overflow(int c) override { if (out->good() && c != traits_type::eof()){ *pptr() = c; // 1 "" , pbump(1); // return sync() == 0 ? c : traits_type::eof(); } return traits_type::eof(); } virtual int sync() override { if (pptr() == pbase()) // , return 0; ptrdiff_t sz = pptr() - pbase(); //, // *out << startb; out->write(pbase(), sz); *out << endb; if (out->good()){ pbump(-sz); // return 0; } return -1; } public: blockoutputbuf(ostream &_out, size_t _bufsize, string _startb, string _endb) : out(&_out), buffer(_bufsize), startb(_startb), endb(_endb) { char_type *buf = buffer.data(); setp(buf, buf + (buffer.size() - 1)); // -1 , overflow() } }; int main(int argc, char **argv){ const char str1[] = "In 4 bytes contains 32 bits"; const char str2[] = "Unix time starts from Jan 1, 1970"; blockoutputbuf buf(cout, 10, "<start>", "<end>\n"); ostream blockoutput(&buf); cout << "Original: '" << str1 << "'" << endl; cout << "Written to blockoutputbuf: '"; blockoutput << str1; blockoutput.flush(); //"" , str1 cout << "'" << endl; cout << "Original: '" << str2 << "'" << endl; cout << "Written to blockoutputbuf: '"; blockoutput << str2; blockoutput.flush(); cout << "'" << endl; return 0; }
overflow()
: the actual buffer size is always 1 element larger than the streambuf "thinks". This allows you to place the “not fit” character passed to the overflow
function and not complicate the code with its specific processing.Original: 'In 4 bytes contains 32 bits' Written to blockoutputbuf: '<start> In 4 bytes <end> <start> contains <end> <start> 32 bits <end> ' Original: 'Unix time starts from Jan 1, 1970' Written to blockoutputbuf: '<start> Unix time <end> <start> starts fro <end> <start> m Jan 1, 1 <end> <start> 970 <end> '
#include <iostream> #include <string> #include <vector> #include <cstdio> #include <cstdlib> using namespace std; class cfilebuf : public streambuf { private: vector<char_type> buffer; FILE *file; protected: virtual int underflow() override { if (!file) return traits_type::eof(); if (gptr() < egptr()) // , return *gptr(); char_type *start = eback(); // , size_t rd = fread(start, sizeof(char_type), buffer.size(), file); // , setg(start, start, start + rd); return rd > 0 ? *gptr() : traits_type::eof(); } public: cfilebuf(size_t _bufsize) : buffer(_bufsize), file(nullptr) { char_type *start = buffer.data(); char_type *end = start + buffer.size(); setg(start, end, end); // eback = start, gptr = end, egptr = end //.. gptr == egptr, } ~cfilebuf(){ close(); } bool open(string fn){ close(); file = fopen(fn.c_str(), "r"); return file != nullptr; } void close(){ if (file){ fclose(file); file = nullptr; } } }; int main(int argc, char **argv){ cfilebuf buf(10); istream in(&buf); string line; buf.open("file.txt"); while (getline(in, line)){ cout << line << endl; } return 0; }
streambuf
class:way
.ios_base::in
(read position) and ios_base::out
(write position). Note that the argument is a bitmask: i.e. may contain one of the values, or both .ios_base::beg
(from the beginning of the stream), ios_base::cur
(from the current position) or ios_base::end
(from the end of the stream). virtual streampos seekpos(streampos sp, ios_base::openmode which) override { if (!(which & ios_base::in)) return streampos(-1); return fill_buffer_from(sp); } virtual streampos seekoff(streamoff off, ios_base::seekdir way, ios_base::openmode which) override { if (!(which & ios_base::in)) return streampos(-1); switch (way){ default: case ios_base::beg: return fill_buffer_from(off, SEEK_SET); case ios_base::cur: return fill_buffer_from(pos_base + gptr() - eback() + off, SEEK_SET); // case ios_base::end: return fill_buffer_from(off, SEEK_END); } }
pos_base
field the pos_base
is stored in the file from which the data was loaded into the buffer.fill_buffer_from
function takes over the fill_buffer_from
. Its implementation is as follows: streampos fill_buffer_from(streampos newpos, int dir = SEEK_SET){ if (!file || fseek(file, newpos, dir) == -1) return -1; long pos = ftell(file); if (pos < 0) return -1; pos_base = pos; char_type *start = eback(); size_t rd = fread(start, sizeof(char_type), buffer.size(), file); setg(start, start, start + rd); return rd > 0 && pos_base >= 0 ? pos_base : streampos(-1); }
istream
has unget()
and putback(character)
methods. In the streambuf
class streambuf
if the character returned to the stream matches the previous one in the buffer, no additional calls occur. However, if the characters do not match or the buffer pointer is at the very beginning, then a function is called to handle this situation:c
character returned to the stream does not match the character in the buffer at the previous position (or it does not exist).pbackfail
: virtual int pbackfail(int c) override { // if (pos_base <= 0 || gptr() > eback()) return traits_type::eof(); // , if (fill_buffer_from(pos_base - 1L) == -1) return traits_type::eof(); if (*gptr() != c){ gbump(1); return traits_type::eof(); } return *gptr(); }
pbackfail
called pbackfail
data will be re-read from the file to the buffer for the sake of just one character - the previous one. But the goal of this article is to understand the principle of operation, and not the competition in the performance of implementations. #include <iostream> #include <string> #include <vector> #include <cstdio> #include <cstdlib> using namespace std; class cfilebuf : public streambuf { private: vector<char_type> buffer; FILE *file; streampos pos_base; // eback streampos fill_buffer_from(streampos newpos, int dir = SEEK_SET) { if (!file || fseek(file, newpos, dir) == -1) return -1; // eback long pos = ftell(file); if (pos < 0) return -1; pos_base = pos; char_type *start = eback(); // , size_t rd = fread(start, sizeof(char_type), buffer.size(), file); // , setg(start, start, start + rd); return rd > 0 && pos_base >= 0 ? pos_base : streampos(-1); } protected: virtual int underflow() override { if (!file) return traits_type::eof(); if (gptr() < egptr()) // , return *gptr(); streampos pos; if (pos_base < 0) { // , pos = fill_buffer_from(0); } else { // pos = fill_buffer_from(pos_base + egptr() - eback()); } return pos != streampos(-1) ? *gptr() : traits_type::eof(); } // ios_base::in // ios_base::out ( ) virtual streampos seekpos(streampos sp, ios_base::openmode which) override { if (!(which & ios_base::in)) return streampos(-1); return fill_buffer_from(sp); } // : , virtual streampos seekoff(streamoff off, ios_base::seekdir way, ios_base::openmode which) override { if (!(which & ios_base::in)) return streampos(-1); switch (way) { default: case ios_base::beg: return fill_buffer_from(off, SEEK_SET); case ios_base::cur: return fill_buffer_from(pos_base + gptr() - eback() + off); // case ios_base::end: return fill_buffer_from(off, SEEK_END); } } virtual int pbackfail(int c) override { // gptr > eback, , // , if (pos_base <= 0 || gptr() > eback()) return traits_type::eof(); // , if (fill_buffer_from(pos_base - streampos(1L)) == streampos(-1)) return traits_type::eof(); if (*gptr() != c) { gbump(1); // , return traits_type::eof(); } return *gptr(); } public: cfilebuf(size_t _bufsize) : buffer(_bufsize), file(nullptr), pos_base(-1) { char_type *start = buffer.data(); char_type *end = start + buffer.size(); setg(start, end, end); // eback = start, gptr = end, egptr = end } ~cfilebuf() { close(); } bool open(string fn) { close(); file = fopen(fn.c_str(), "r"); return file != nullptr; } void close() { if (file) { fclose(file); file = nullptr; } } }; void read_to_end(istream &in) { string line; while (getline(in, line)) { cout << line << endl; } } int main(int argc, char **argv) { cfilebuf buf(10); istream in(&buf); buf.open("file.txt"); read_to_end(in); in.clear(); // cout << endl << endl << "Read last 6 symbols:" << endl; in.seekg(-5, ios_base::end); // , 5 in.seekg(-1, ios_base::cur); // 6, :) read_to_end(in); in.clear(); cout << endl << endl << "Read all again:" << endl; in.seekg(0); read_to_end(in); in.clear(); in.seekg(2); // 3- ( 2-) in.get(); in.putback('b'); in.putback('a'); // pbackfail() in.putback('H'); string word; in >> word; cout << endl << endl << "Read word after putback(): " << word << endl; return 0; }
fread
and fwrite
. If suddenly for block reading or writing in your case, you can implement a more efficient algorithm than character-by-character processing, then these methods are for you.basic_streambuf
template class and use the character type you need. Type aliases such as char_type
, int_type
, pos_type
, etc. will help you in the implementation. It is preferable to use them, since they always correspond to the types with which the library implementation of streambuf
.Source: https://habr.com/ru/post/326578/
All Articles