basic_strstream

高木です。こんにちは。
先日、フリーウェアでも作ろうかという話をしました。
とはいえ、そんな大がかりなものも作れないので、まずは簡単なものから着手することにしました。
今回はその中間報告です。
C++の標準ライブラリに、<strstream>というヘッダがあります。
このヘッダでは、strstreambuf、istrstream、ostrstream、そしてstrstreamという4つのクラスが定義されています。
このライブラリ、現在では非推奨になっていて、過去との互換性のためだけに用意されています。
以前なら、規格上は非推奨になっていても、実際にはなくなることはないだろうとタカをくくっていることができました。
ところが、最近では本当に規格から削除されてしまう破壊的変更の事例が出始めています。
さすがに、そのような状況で非推奨のライブラリに依存したコードを書くのは気が引けます。
それに、処理系によって、をインクルードするだけで非推奨の旨の警告が出て、ウザいことこの上ないのです。
<strstream>が非推奨なら、じゃあ何を使えばいいかというと、<sstream>を使えということになっています。
これは、basic_stringに対する入出力を行うもので、テンプレートなのでcharだけでなく他の文字型も使えます。
確かに使い勝手はこちらのほうが勝っています。
けれども、効率面を考えるとのクラス群に軍配が上がることも多いのです。
単純な配列に文字列を書き込みたい場合や、生の文字列をパースしたい場合なんかがまさにそうです。
sprintfやsscanfの代わりに使うということですね。
そんな状況にヒヤヒヤしながらを使い続けるのも嫌だし、諦めて使わないのもシャクです。
というわけで、標準規格があてにならないなら、自分で実装してしまおうことにしました。
そして、せっかくなので、テンプレート化することでchar以外の文字型も使えるようにしました。
以下、非常に長いですが、コードを貼っておきます。
また、最後にダウンロード用のリンクも貼っておきますので、興味がある方はどうぞ。
// dradnats/strstream // C++規格では非推奨になっている<strstream>を、リメイクかつテンプレート化しました。 // 名前空間以外は標準ライブラリと互換性をもたせています。 // 使い方については、ISO/IEC 14882:2003 D.7 char* streamsを参照してください。 #ifndef DRADNATS_STRSTREAM_ #define DRADNATS_STRSTREAM_ #pragma once #include <cstddef> #include <cstdlib> #include <limits> #include <new> #include <streambuf> #include <istream> #include <ostream> namespace dradnats { namespace detail { template <class charT> struct to_compatible_char_type; template <> struct to_compatible_char_type<signed char> { typedef char type; }; template <> struct to_compatible_char_type<unsigned char> { typedef char type; }; } template <class charT, class traits = std::char_traits<charT> > class basic_strstreambuf : public std::basic_streambuf<charT, traits> { typedef std::basic_streambuf<charT, traits> base; public: typedef charT char_type; typedef traits traits_type; typedef typename std::basic_streambuf<charT, traits>::int_type int_type; typedef typename std::basic_streambuf<charT, traits>::pos_type pos_type; typedef typename std::basic_streambuf<charT, traits>::off_type off_type; explicit basic_strstreambuf(std::streamsize alsize_arg = 0) : strmode(dynamic), alsize(alsize_arg), palloc(0), pfree(0) { std::streamsize n = alsize > 16 ? alsize : 16; if (char_type* buf = allocate(n)) init(buf, n, buf); } basic_strstreambuf(void* (*palloc_arg)(std::size_t), void (*pfree_arg)(void*)) : strmode(dynamic), alsize(0), palloc(palloc_arg), pfree(pfree_arg) { std::streamsize n = 16; if (char_type* buf = allocate(n)) init(buf, n, buf); } basic_strstreambuf(char_type* gnext_arg, std::streamsize n, char_type* pbeg_arg = 0) { init(gnext_arg, n, pbeg_arg); } basic_strstreambuf(const char_type* gnext_arg, std::streamsize n) { init(gnext_arg, n); } template <class T> basic_strstreambuf(T* gnext_arg, std::streamsize n, T* pbeg_arg = 0) { typedef typename detail::to_compatible_char_type<T>::type target_type; init(reinterpret_cast<target_type*>(gnext_arg), n, reinterpret_cast<target_type*>(pbeg_arg)); } template <class T> basic_strstreambuf(const T* gnext_arg, std::streamsize n) { typedef typename detail::to_compatible_char_type<T>::type target_type; init(reinterpret_cast<const target_type*>(gnext_arg), n); } virtual ~basic_strstreambuf() { if ((this->strmode & allocated) != 0 && (this->strmode & frozen) == 0) deallocate(base::eback()); } void freeze(bool freezefl = true) { if ((this->strmode & dynamic) != 0) { if (freezefl) this->strmode |= frozen; else this->strmode &= ~frozen; } } char* str() { freeze(); return gbeg(); } int pcount() { if (pnext() == 0) return 0; return pnext() - pbeg(); } protected: virtual int_type overflow(int_type c) { if (this->strmode & constant) return traits_type::eof(); if (c != traits_type::eof()) { if (pnext() == pend()) { if (!expand()) return traits_type::eof(); } *pnext() = traits_type::to_char_type(c); incr_pnext(1); return traits_type::to_int_type(c); } return traits_type::not_eof(c); } virtual int_type pbackfall(int_type c) { if (gnext() != gbeg()) { if (c != traits_type::eof()) { if (c == gnext()[-1]) { incr_gnext(-1); return c; } else if (this->strmode & constant) { incr_gnext(-1); *gnext() = traits_type::to_char_type(c); return c; } } return traits_type::not_eof(c); } return traits_type::eof(); } virtual int_type underflow() { if (gnext() == gend() && pnext() != 0 && pnext() > gend()) base::setg(gbeg(), gnext(), pnext()); if (gnext() != gend()) return traits_type::to_int_type(*gnext()); return traits_type::eof(); } virtual pos_type seekoff(off_type off, std::ios_base::seekdir way, std::ios_base::openmode which) { const bool in = (which & std::ios_base::in) != 0; const bool out = (which & std::ios_base::out) != 0; if (in && out && way != std::ios_base::beg && way != std::ios_base::end) return pos_type(off_type(-1)); if ((!in && !out) || (out && pnext() == 0) || gnext() == 0) return pos_type(off_type(-1)); char_type* seeklow = gbeg(); char_type* seekhigh = pend() ? pend() : gend(); off_type newoff; if (way == std::ios_base::beg) newoff = 0; else if (way == std::ios_base::cur) newoff = out ? pnext() - seeklow : gnext() - seeklow; else if (way == std::ios_base::end) newoff = seekhigh - seeklow; else return pos_type(off_type(-1)); off += newoff; if (off < 0 || seekhigh - seeklow < off) return pos_type(off_type(-1)); if (out) { if (seeklow + static_cast<std::ptrdiff_t>(off) < pbeg()) { base::setp(seeklow, pend()); incr_pnext(int(off)); } else { base::setp(pbeg(), pend()); incr_pnext(int(off - (pbeg() - seeklow))); } } if (in) { char_type* end; if (off <= gend() - seeklow) end = gend(); else if (off < pnext() - seeklow) end = pnext(); else end = pend(); base::setg(seeklow, seeklow + static_cast<std::ptrdiff_t>(off), end); } return pos_type(newoff); } virtual pos_type seekpos(pos_type sp, std::ios_base::openmode which) { return seekoff(sp, std::ios_base::beg, which); } virtual std::basic_streambuf<char_type, traits>* setbuf(char_type* s, std::streamsize n) { return this; } private: void init(char_type* gnext_arg, std::streamsize n, char_type* pbeg_arg) { this->strmode = 0; this->alsize = 0; this->palloc = 0; this->pfree = 0; std::streamsize N; if (n > 0) N = n; else if (n == 0) N = traits_type::length(gnext_arg); else N = std::numeric_limits<int>::max(); if (pbeg_arg == 0) { base::setg(gnext_arg, gnext_arg, gnext_arg + N); } else { base::setg(gnext_arg, gnext_arg, pbeg_arg); base::setp(pbeg_arg, pbeg_arg + N); } } void init(const char_type* gnext_arg, std::streamsize n) { init(const_cast<char_type*>(gnext_arg), n, 0); this->strmode = constant; } char_type* gnext() { return base::gptr(); } void incr_gnext(int n) { base::gbump(n); } char_type* gbeg() { return base::eback(); } char_type* gend() { return base::egptr(); } char_type* pnext() { return base::pptr(); } void incr_pnext(int n) { base::pbump(n); } char_type* pbeg() { return base::pbase(); } char_type* pend() { return base::epptr(); } bool expand() { if ((this->strmode & (dynamic | frozen)) != dynamic) return false; std::streamsize prev_n = pend() - pbeg(); std::streamsize n = prev_n * 2; if (n < this->alsize) n = this->alsize; if (n < 1) n = 1; if (char_type* buf = allocate(n)) { traits_type::copy(buf, pbeg(), prev_n); char_type* prev_buf = pbeg(); std::ptrdiff_t offg = -1; if (gnext() != 0) offg = gnext() - gbeg(); base::setp(buf, buf + n); incr_pnext((int)prev_n); if (offg >= 0) base::setg(buf, buf + offg, buf + (offg > prev_n ? offg : prev_n)); deallocate(prev_buf); return true; } return false; } char_type* allocate(std::size_t n) { char_type* s; if (this->palloc != 0) s = static_cast<char_type*>((*this->palloc)(sizeof(char_type) * n)); else s = new(std::nothrow) char_type[n]; return s; } void deallocate(char_type* s) { if (this->pfree != 0) (*this->pfree)(s); else delete[] s; } private: typedef unsigned char strstate; static const strstate allocated = 0x01; static const strstate constant = 0x02; static const strstate dynamic = 0x04; static const strstate frozen = 0x08; strstate strmode; std::streamsize alsize; void* (*palloc)(std::size_t); void (*pfree)(void*); }; template <class charT, class traits = std::char_traits<charT> > class basic_istrstream : public std::basic_istream<charT, traits> { typedef std::basic_istream<charT, traits> base; public: typedef charT char_type; typedef traits traits_type; explicit basic_istrstream(const char_type* s) : std::basic_istream<charT, traits>(0), sb(s) { base::init(&this->sb); } explicit basic_istrstream(char_type* s) : std::basic_istream<charT, traits>(0), sb(s) { base::init(&this->sb); } basic_istrstream(const char_type* s, std::streamsize n) : std::basic_istream<charT, traits>(0), sb(s, n) { base::init(&this->sb); } basic_istrstream(char_type* s, std::streamsize n) : std::basic_istream<charT, traits>(0), sb(s, n) { base::init(&this->sb); } virtual ~basic_istrstream() { } basic_strstreambuf<charT, traits>* rdbuf() const { return const_cast<basic_strstreambuf<charT, traits>*>(&this->sb); } char_type* str() { return rdbuf()->str(); } private: basic_strstreambuf<charT, traits> sb; }; template <class charT, class traits = std::char_traits<charT> > class basic_ostrstream : public std::basic_ostream<charT, traits> { typedef std::basic_ostream<charT, traits> base; public: typedef charT char_type; typedef traits traits_type; basic_ostrstream() : std::basic_ostream<charT, traits>(0) { base::init(&this->sb); } basic_ostrstream(char_type* s, int n, std::ios_base::openmode mode = std::ios_base::out) : std::basic_ostream<charT, traits>(0), sb(s, n, (mode & std::ios_base::app) ? s : s + traits_type::length(s)) { base::init(&this->sb); } virtual ~basic_ostrstream() { } basic_strstreambuf<charT, traits>* rdbuf() const { return const_cast<basic_strstreambuf<charT, traits>*>(&this->sb); } void freeze(bool freezefl = true) { rdbuf()->freeze(freezefl); } char_type* str() { return rdbuf()->str(); } int pcount() const { return rdbuf()->pcount(); } private: basic_strstreambuf<charT, traits> sb; }; template <class charT, class traits = std::char_traits<charT> > class basic_strstream : public std::basic_iostream<charT, traits> { typedef std::basic_iostream<charT, traits> base; public: typedef charT char_type; typedef traits traits_type; basic_strstream() : std::basic_iostream<charT, traits>(0) { base::init(&this->sb); } basic_strstream(char_type* s, int n, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) : std::basic_iostream<charT, traits>(0), sb(s, n, (mode & std::ios_base::app) ? s : s + traits_type::length(s)) { base::init(&this->sb); } virtual ~basic_strstream() { } basic_strstreambuf<charT, traits>* rdbuf() const { return const_cast<basic_strstreambuf<charT, traits>*>(&this->sb); } void freeze(bool freezefl = true) { rdbuf()->freeze(freezefl); } char_type* str() { return rdbuf()->str(); } int pcount() const { return rdbuf()->pcount(); } private: basic_strstreambuf<charT, traits> sb; }; typedef basic_strstreambuf<char> strstreambuf; typedef basic_istrstream<char> istrstream; typedef basic_ostrstream<char> ostrstream; typedef basic_strstream<char> strstream; typedef basic_strstreambuf<wchar_t> wstrstreambuf; typedef basic_istrstream<wchar_t> wistrstream; typedef basic_ostrstream<wchar_t> wostrstream; typedef basic_strstream<wchar_t> wstrstream; } #endif // DRADNATS_STRSTREAM_ // Copyright © 2017 by TAKAGI Nobuhisa // takagi@cloverfield.jp // どなたでも、自己の責任においてこのプログラムを使用していただいてかまいません。
このライブラリを使うには、単にヘッダーファイルをインクルードするだけでOKです。
最初に書いたように、今回はあくまで中間報告です。
主だった機能はテストしましたが、細部の検証はまだできていません。
それを承知で使ってみようという方は、自己責任で自由に使っていただいてかまいません。
もし不具合を見つけられた場合は、なるべく報告していただけると助かります。
名前空間等に使っているdradnatsは、standardの逆スペルです。
今後、私がこの名前を使う機会が増えてくるかと思います。
あと、コメントがまったく入っていないのは仕様です。
標準規格にかなり詳細な記述がありますので、テンプレート化したとはいえ、下手にコメントを入れるのはうるさいだけだからです。