C++のiostream

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 に示してある 私の実装資料を参照のこと。)

  • virtual int underflow(void);
  • virtual int uflow(void);
  • virtual int overflow(int c = std::char_traits::eof());
  • virtual int sync(void);

各関数の果たすべき役割を簡単に説明すると、

  • underflow() は読み出し位置を進めずに1文字読み出す関数、
  • uflow() は1文字読み出した後、読み出し位置を1文字分進める関数、
  • overflow(int c) は c を書き込み位置に書き出し、書き込み位置を1文字分進める関数、
  • sync() は未出力の書き込み buffer の中身を出力先に送り出す関数

である。 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 用にするには、

  • Winsock の load/unload
  • dup() を DuplicateHandle? で実装
  • close() を closesocket() に変更

する必要がある。

更に型を正しくするには、

  • socket descriptor は int ではなく SOCKET
  • socket descriptor の不正を表す値は -1 ではなく INVALID_SOCKET
  • send()、recv() の失敗を表す戻り値は -1 ではなく、SOCKET_ERROR

である(なお、実態は singned/unsigned の違いはあったかも知れないが 実は同じ)。


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2009-08-31 (月) 11:38:45