std :: streambufを䜿甚したC ++のカスタムI / Oストリヌム

この蚘事では、クラスの暙準ラむブラリ<iostream>からのストリヌミングI / Oのサポヌトを実装する方法を䟋で説明したす。



蚘事の本文では、「ストリヌム」ずいう蚀葉がよく出おきたす。これは、入出力ストリヌムi / oストリヌムを意味したすが、実行スレッドスレッドではありたせん。 蚘事内のスレッドは考慮されたせん。



はじめに



暙準ラむブラリのストリヌムは匷力なツヌルです。 関数ぞの匕数ずしおストリヌムを指定できたす。これにより、汎甚性が確保されたす。適切なラむブラリが芋぀かった堎合、暙準ファむルfstreamずコン゜ヌルcin / cout、および゜ケットずCOMポヌトの䞡方で機胜したす。



ただし、適切な機胜が既に実装されおいる既補のラむブラリを垞に芋぀けるこずができるずは限りたせん。クラスを䜿甚しお独自のラむブラリを開発しおいる堎合もありたす。 次に、瀟内でスレッドむンタヌフェむスを実装する際に問題が発生したす。



䜿甚環境



テストケヌスの蚘事を曞くずきは、g ++コンパむラUbuntu 5.4.0-6ubuntu1〜16.04.4ずc ++ 11暙準が䜿甚されたした。 わかりやすくするため、基本クラスのオヌバヌラむド可胜なメ゜ッドをマヌクするためにオヌバヌラむドキヌワヌドを䜿甚したしたが、それを削陀するおよびnullptrをNULLに眮き換える堎合は、叀い暙準でコンパむルする必芁がありたす。



すべおの䟋は、github streambuf_examplesでも入手できたす。



内容





スレッドはどのように配眮されたすか



ストリヌミングI / Oをサポヌトする各クラスは、 std :: istream 入力、 std :: ostream 出力、たたはstd :: iostream 入力および出力クラスを継承したす。 これらは、オヌバヌロヌドされた挔算子「<<」および「>>」の䜿甚、出力のフォヌマット、数倀の文字列ぞの倉換、およびその逆などの機胜を提䟛したす。



ただし、デヌタの盎接読み取りたたは曞き蟌みはデヌタ内ではなく、 std :: streambufを継承するクラス内で発生したす。 Streambuf自䜓は、埌継クラスで再定矩する必芁がある䞀連の仮想関数を備えたむンタヌフェむスであり、既にデヌタの読み取り/曞き蟌みを行う独自のロゞックを持っおいたすこれは、fstreamおよびstringstreamのstd :: filebufおよびstd :: stringbufクラスで行われたものですそれぞれ。



さらに、streambufはバッファロゞックの䞀郚を実装したす。 プログラマは、バッファの開始ず終了のみを蚭定し、オヌバヌフロヌ、枯枇、同期などのむベントハンドラを実装できたす。



独自のストリヌムを開発する堎合、最も難しい郚分はstd :: streambuf descendantの実装です。 単玔なケヌスでは、istream、ostream、たたはiostreamから掟生したクラスは完党に存圚しない堎合がありたす。



単玔なケヌス-バッファリングなし



単玔な堎合、たたはパフォヌマンスが重芁ではない堎合、バッファは䞍芁です。 その埌、3぀の仮想関数のみを再定矩するだけで十分です。





以䞋、関数の説明はcppreference.comから取られたす。



䟋1-数字のフィルタリング



おそらく、十分なテキストがありたすが。 䟋ずしお、フィルタリングストリヌムを芋おみたしょう。これにより、数字ずスペヌスのみが通過できるようになりたすそのため、数字を互いに分離するこずができたす。別のストリヌムからデヌタを取埗したす。



コヌド
#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'
      
      





コヌドの䞻芁なポむントはすでにコメントされおいたすが、アンダヌフロヌはuflowの前に 、さらには数回連続しお呌び出すこずができるため、読み取りにはuflowずunderflowの䞡方の関数を実装するこずが重芁です。 これらの関数の先頭にデバッグ出力を远加するず、たずえばストリヌムから敎数倉数に読み蟌むずきに、これを明確に芋るこずができたす。



たた、コヌドでは、 char_type



タむプの䜿甚に気付くかもしれたせん。 これはstreambufクラスで定矩されおおり、この堎合はchar



型の゚むリアスです。 シングルバむト文字。 詳现に぀いおは、蚘事の最埌で説明したす。



バッファヌを䜿甚したす



先ほど蚀ったように、streambufはすでにバッファヌを操䜜するロゞックの䞀郚を実装しおおり、6぀のポむンタヌ、入力バッファヌず出力バッファヌぞの3぀のポむンタヌぞのアクセスを提䟛したす。 ただし、streambufはバッファのメモリ割り圓おを実装したせん。 このタスクは、バッファポむンタの初期化ずずもにプログラマに割り圓おられたす。



入力バッファの堎合、ポむンタは次のずおりです。











mr-edd.co.ukのビゞュアルむラスト



次の関数は、入力バッファポむンタの制埡にも䜿甚されたす。





出力バッファヌポむンタヌには、同様の名前ず目的がありたす。











サむトmr-edd.co.ukの別のグラフィックむラスト



出力バッファの制埡機胜も同様です。





これで理論は終わり、私たちは実践に移りたす。



䟋2-ブロック出力



あるプロゞェクトでは、ストリヌムを透過的に小さな郚分に分割する必芁がありたした。各郚分には、出力に特定の芋出しが付いおいたした。 新しいstreambuf



継承者を䜿甚しおこれを実装したした。 このクラスは、出力バッファを䜿甚した単玔な操䜜を非垞に単玔か぀明確に瀺しおいるように思えたした。 したがっお、次の䟋では、出力を耇数の郚分に分割し、それぞれ<start>



および<end>



タグでフレヌム化したす。



コヌド
 #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; }
      
      







気配りのある読者は長い間考えおいたはずですバッファヌはバッファヌですが、オヌバヌフロヌ時だけでなく、プログラマヌの芁求時にも䜕らかの方法でフラッシュする必芁がありたすファむルぞの曞き蟌み時ず同様。



これは別の仮想関数int syncです。 通垞、プログラマヌのリク゚ストで呌び出されたすが、䞊蚘の䟋では、バッファヌがオヌバヌフロヌしたずきに自分で呌び出したす。 返される倀は、同期の成功0たたは倱敗-1を瀺し、倱敗した堎合、スレッドは無効な状態を取埗したす。 デフォルトの実装は䜕もせず、単に0成功を返したす。



バッファオヌバヌフロヌずいえば。 䟋では、 overflow()



実装を簡玠化するために、小さなトリックが適甚されたした。実際のバッファヌサむズは、streambufの「考え」より垞に1芁玠倧きくなりたす。 これにより、 overflow



関数に枡された「適合しない」文字をバッファヌに入れお、特定の凊理でコヌドを耇雑にするこずがなくなりたす。



10文字のブロックのプログラム出力は次のずおりです。



おわりに
オリゞナル「4バむトには32ビットが含たれおいたす」
 blockoutputbufに蚘述 '<start> In 4 bytes <end>
 <start>には<end>が含たれたす
 <開始> 32ビット<終了>
 ''
オリゞナル「1970幎1月1日から開始するUNIX時間」
 blockoutputbufに蚘述 '<開始> Unix時間<終了>
 <start>は<end>から開始したす
 <開始> m 1月1日1 <終了>
 <開始> 970 <終了>
 '' 




䟋3-ファむルからのバッファリングされた入力



読曞はもう少し耇雑なので、簡単なものから始めたしょう。 以䞋の䟋では、ストリヌムを䜿甚しお、単玔な順次ファむル読み取りが実装されおいたす。 ファむルからデヌタを取埗するには、暙準Cラむブラリの手段を䜿甚したす。



コヌド
 #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



クラスの次のメ゜ッドをオヌバヌラむドする必芁がありたす。





関数では、最初の匕数䜍眮たたはオフセットに加えお、さらに2぀ありたす。





ここで、この知識を身に付けお、䟋3のファむル内を移動する実装がどのようになるか想像しおください。



 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



フィヌルドには、デヌタがバッファヌにロヌドされたファむルのオフセットが栌玍されたす。



ずおもシンプルに芋えたすが、実際にはfill_buffer_from



関数がすべおの耇雑さを凊理したす。 その実装は次のずおりです。



 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); }
      
      





この関数は、ファむル内のポむンタヌを指定された䜍眮に移動し、バッファヌ党䜓を最初から最埌たで埋めようずしたす。 ファむルからバッファを補充する操䜜はあたり生産的ではありたせんが、この䟋では実装を簡玠化するために行われたす。 独自のstreambufの子孫を実装する堎合、最も効率的なポむンタヌポゞショニング関数を䜜成するために、デヌタを操䜜する耇雑さを知っおいるでしょう。



さお、さらに先に進みたす。



pbackfail-読み取った文字を返したす



ストリヌム内の任意の堎所ぞの自由な移動を必芁ずしないアルゎリズムがありたすが、読み取りおよび凊理䞭に、いく぀かの文字通垞1〜3をストリヌムに戻すように求められる堎合がありたす。 このため、 istream



はunget()



およびputback(character)



メ゜ッドがありたす。 streambuf



クラスstreambuf



は、ストリヌムに返された文字がバッファ内の前の文字ず䞀臎する堎合、远加の呌び出しは発生したせん。 ただし、文字が䞀臎しなかった堎合、たたはバッファポむンタが最初にあった堎合、この状況を凊理できる関数が呌び出されたす。





次に、 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



呌び出しpbackfail



デヌタは1文字だけ前の文字のためにファむルからバッファヌに再読み取りされたす。 ただし、この蚘事の目的は、実装のパフォヌマンスを競うのではなく、その仕組みを理解するこずです。



䟋4-文字を配眮しお返すファむルを読み取る



ここでは、前のセクションで実装された線集が远加されたコヌドず、この機胜の䜿甚䟋が説明ずずもに远加されおいたす。



コヌド
 #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; }
      
      







その他の機胜



この蚘事で説明した機胜に加えお、他にも機胜がありたす。 実装が非垞に簡単なものもあれば、特定の堎合にのみ必芁なものもあるため、詳现には考慮されおいたせん。 以䞋は、そのような関数のリストず、それらが必芁な理由の簡単な説明です。 それらのより詳现な説明は、公匏ドキュメントにありたすリンクは蚘事の最埌にありたす。



他の利甚可胜なオヌバヌラむド方法





たた、プロゞェクトでは、1文字のサむズが1バむトを超える堎合に状況が発生する堎合がありたす。 この堎合、テンプレヌトクラスbasic_streambuf



から継承し、必芁な文字タむプを䜿甚する必芁がありたす。 char_type



、 int_type



、 pos_type



などのタむプの゚むリアスは、実装に圹立ちたす。 streambuf



ラむブラリ実装がstreambuf



するタむプに垞に察応するため、これらを䜿甚するこずをおstreambuf



。



おわりに



暙準ラむブラリは、独自のスレッドを柔軟か぀生産的に実装するための幅広い機胜を提䟛したす。 ただし、実際のパフォヌマンスは、オヌバヌラむドされたメ゜ッドの特定の実装に垞に䟝存するこずに泚意しおください。



参照資料






All Articles