C++ iostream 実装例 その 1 †C++ の iostream 実装例として、効率無視の超簡単、超手抜き TCP/IP stream の iostream 化を示す。実用性というよりは、最低限実装すべき機能の (私が忘れた場合に備えての)メモとして残しておくものである。 何をすればよいか †C++ の iostream は(効率を無視すれば)実装が極めて簡単にできる 構造になっている。 極端なことを言えば、必要なのは streambuf の派生 class を作る ことだけである。 実装のために iostream class の内容を知る必要は全く無いし、 iostream の派生 class を作る必要すら無い。 streambuf class の派生 class を作り、その object の streambuf 部分の pointer を iostream の constructor に渡せば、 それだけで iostream として使える。 (具体的には次節 C++ iostream 実装例 その 2 参照)。 最小限実装すべきもの †streambuf から派生させた class で実装する 必要があるのは、以下の4つの protected member 関数である。(詳しくは、C++のiostream に示してある 私の実装資料を参照のこと。)
各関数の果たすべき役割を簡単に説明すると、
である。 overflow(int c) で常に出力先へ出力するのであれば、sync() の実装は 必要無い。 実装例 †というわけで、実装してみよう。実装するのは、TCP/IP の connection に 対して行おう。長い source code は見るのが鬱陶しいので、 TCP/IP の connection を張るところまでは済んでいるものとして、 constructor にその socket を渡すものとしよう。 member 変数としては、constructor で渡される socket descriptor を 保持しておく m_socket と、上記 underflow() と uflow() を実装する ための1文字分の(先読み)buffer m_gpend_char と buffer 内に文字があるかどうかを示す flag m_gpend を用意する。 (g は get 用の意味、std::streambuf 内の命名法を真似てみた)。 // tcpbuf.h #ifndef TCPBUFFER_H_INCLUDED #define TCPBUFFER_H_INCLUDED #include <streambuf> namespace mynetlibrary { class tcpbuffer : public std::streambuf { public: tcpbuffer(int sock); virtual ~tcpbuffer(void); int state(void) const; protected: virtual int underflow(void); virtual int uflow(void); virtual int overflow(int c = std::char_traits<char>::eof()); virtual int sync(void); protected: int m_socket; bool m_gpend; int m_gpend_char; }; } // namespace mynetlibrary #endif // TCPBUFFER_H_INCLUDED で、定義(source)。ここで、socket descriptor をどういう扱いにするか 少し悩むところである。ここでは一応 thread で使う安全性も 考えて dup() を使って copy しておく。従って、呼び出し側でも、 それ以上必要無ければ、close() しておくこと。 なお、以下の実装で、buf[] の size を 1 ではなく 2 としている理由だが、 大きさ 1 の"配列"は気持ち悪いという筆者の主観もあるが、もう一つは text mode での CR LF の扱いを将来入れたくなるかも知れないという 思惑も働いている。長くなるので載せないが、実際やってみたこともある。 // tcpbuf.cpp #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include "tcpbuf.h" namespace mynetlibrary { tcpbuffer::tcpbuffer(int sock) { m_gpend = false; m_gpend_char = std::char_traits<char>::eof(); m_socket = ::dup(sock); } tcpbuffer::~tcpbuffer(void) { if(m_socket != -1) ::close(m_socket); } int tcpbuffer::state(void) const { if(m_socket == -1) return -1; return 0; } int tcpbuffer::underflow(void) { unsigned char buf[2]; // Get the next character. The getpoint does not move. if(m_gpend) return m_gpend_char; if(m_socket == -1) return std::char_traits<char>::eof(); if(::recv(m_socket, buf, 1, 0) != 1) return std::char_traits<char>::eof(); int c = (int)buf[0]; m_gpend = true; m_gpend_char = (c & 255); return m_gpend_char; } int tcpbuffer::uflow(void) { int c = underflow(); m_gpend = false; return c; } int tcpbuffer::overflow(int c) { if(c == std::char_traits<char>::eof()) return 0; if(m_socket == -1) return std::char_traits<char>::eof(); unsigned char buf[2]; buf[0] = (unsigned char)(c & 255); if(::send(m_socket, buf, 1, 0) != 1) return std::char_traits<char>::eof(); return c; } int tcpbuffer::sync(void) { return 0; } } // namespace mynetlibrary 次節 "C++ iostream 実装例 その 2" では、この実装を iostream にして 使う例を示そう。 Windows での注意 †Windows 用にするには、
する必要がある。 更に型を正しくするには、
である(なお、実態は singned/unsigned の違いはあったかも知れないが 実は同じ)。 |