メモリをストリームにする

C++ でプログラミングしているときにメモリをファイルのように扱いたい場合というのがある。 元々実行ファイルとは別のファイルとしていたものを実行ファイルに抱え込む必要ができた場合などだ。

POSIX では fmemopen や open_memstream といった関数でそれが出来るのだそうだ。

しかし、 Windows ではそれを可能とする機能は用意されていないようだ。 低水準のレイヤで出来るのならばその方がよい (その上にどんなライブラリやフレームワークが載っても利用できるので) のだが、用意されていないのならば仕方がない。

仕方がないので C++ の言語機能のレイヤで、つまりはファイルを扱う一般的な方法であるところのストリームのインターフェイスを与えることでファイルと同じように扱う方法を考えた。

// memstream.h
#include <istream>

template <class Ch,class Tr=std::char_traits<Ch> >
class basic_mem_streambuf
  : public std::basic_streambuf<Ch,Tr> {
private:
  const Ch* mem;
  const size_t siz;
  const Ch* cur;
  Ch chbuf;
public:
  basic_mem_streambuf(const Ch* mem, const size_t siz)
    : mem(mem), siz(siz), cur(mem) {
    this->setg(&chbuf, &chbuf+1, &chbuf+1);
  }
protected:
  typename Tr::int_type underflow(void) {
    if(cur >= &mem[siz]) {
      return Tr::eof();
    } else {
      chbuf = *cur++;
      this->setg(&chbuf, &chbuf, &chbuf+1);
      return chbuf;
    }
  }
};

template <class Ch,class Tr=std::char_traits<Ch> >
class basic_mem_stream : public std::basic_istream<Ch,Tr> {
private:
  basic_mem_streambuf<Ch, Tr>* buf;
  basic_mem_stream(void);
public:
  basic_mem_stream(const Ch * mem, typename Tr::int_type siz)
    :std::basic_istream<Ch,Tr>(buf=new basic_mem_streambuf<Ch,Tr>(mem, siz)) {
  }
  ~basic_mem_stream(void) {
    delete buf;
  }
};

typedef basic_mem_stream<char> mem_stream;

以下のようにして、対象となるメモリを指定すればその範囲のデータをストリームとして使える。

#include <cstring>
#include <iostream>
#include "memstream.h"

int main(void) {
  const char* a="123\n456";
  int c,d;
  mem_stream b(a, std::strlen(a));
  b>>c>>d;
  std::cout << c << std::endl << d << std::endl;
}

C++ の入出力まわりは不格好だという批判はあるが、様々なデバイスに対する入出力を抽象化するにはそこそこうまくやっていると思う。

Document ID: d5929030205fda0ad79ae9b71bfbbbc2