Hatena::ブログ(Diary)

Life like a clown このページをアンテナに追加 RSSフィード Twitter

2009-05-27

配列やコンテナをストリームとして扱う

手元に配列や(string, vector, deque, などの)コンテナと言う形でデータを保持してるけれども,使用したい関数(メソッド)のインターフェース(引数)は istream と言うケースに遭遇することがあります.このとき,簡単な解決策として stringstream を利用することが挙げられるのですが,元のデータサイズが大きくなるとコピーのコストが高くなります.そこで,配列やコンテナをストリームとして扱うためのクラスを作成してみました.

namespace clx {
    template <
        class CharT,
        class Traits = std::char_traits<CharT>
    >
    class basic_ivstream : public std::basic_istream<CharT, Traits> {
    public:
        typedef basic_ivstreambuf<CharT, Traits> streambuf_type;
        typedef typename streambuf_type::size_type size_type;
        
        template <class Container>
        basic_ivstream(const Container& data);
        
        template <class Type>
        basic_ivstream(const Type* data, size_type n);
        
        virtual ~basic_ivstream() throw();
    };
    
    typedef basic_ivstream<char> ivstream;
}

コンテナや配列をコンストラクタの引数に指定して ivstream を作成すると,指定したデータの範囲内で,read(), seekg(), tellg() などを行うことができるストリームが生成されます.コンテナは,vector のように begin()/end() メソッドを持っており,かつ領域が連続されたメモリ空間内に確保されてある必要があります.また,配列(または,new などで動的に確保した領域)を指定する場合は,第 2 引数に要素数を指定する必要があります.

サンプルコードは以下の通りです.尚,istream, istreambuf の実装は記事の最後に記載します.

#include <iostream>
#include <iterator>
#include <vector>
#include "vstream.h"

template <class InStream, class Type>
bool getdata(InStream& in, Type& dest) {
    typedef typename InStream::char_type char_type;
    in.read(reinterpret_cast<char_type*>(&dest), sizeof(Type));
    if (in.gcount() <= 0) return false;
    return true;
}

int main(int argc, char* argv[]) {
    std::vector<int> v;
    for (int i = 0; i < 100; ++i) v.push_back(i);
    clx::ivstream vs(v);
    
    int data;
    vs.seekg(80);
    vs.seekg(-40, std::ios_base::end);
    while (getdata(vs, data)) std::cout << data << " ";
    std::cout << std::endl;
    
    return 0;
}

ivstream に指定するコンテナは,vector<int> なども指定することができますが,(basic_ivstream<char> の場合)read() や seekg() の引数でサイズを指定する際にはバイト単位で指定する必要があります.

※ivstreambuf の実装では shared_ptr を利用しています.使用する際は,boost::shared_ptr なり std::tr1::shared_ptr なりに修正してご使用下さい.

#ifndef CLX_VSTREAM_H
#define CLX_VSTREAM_H

#include <istream>
#include <streambuf>
#include <string>
#include "shared_ptr.h"

namespace clx {
    /* --------------------------------------------------------------------- */
    //  basic_ivstreambuf
    /* --------------------------------------------------------------------- */
    template <
        class CharT,
        class Traits = std::char_traits<CharT>
    >
    class basic_ivstreambuf : public std::basic_streambuf<CharT, Traits> {
    public:
        typedef size_t size_type;
        typedef CharT char_type;
        typedef typename Traits::pos_type pos_type;
        typedef typename Traits::off_type off_type;
        
        template <class Container>
        basic_ivstreambuf(const Container& data) :
            std::basic_streambuf<CharT, Traits>(),
            p_(new data_container<Container>(data)) {
            this->setg(p_->begin(), p_->begin(), p_->end());
        }
        
        template <class Type>
        basic_ivstreambuf(const Type* data, size_type n) :
            std::basic_streambuf<CharT, Traits>(),
            p_(new data_array<Type>(data, n)) {
            this->setg(p_->begin(), p_->begin(), p_->end());
        }
        
        virtual ~basic_ivstreambuf() throw() {}
        
    protected:
        virtual pos_type seekoff(off_type off, std::ios_base::seekdir way,
            std::ios_base::openmode which = std::ios_base::in) {
            if (which & std::ios_base::in) {
                pos_type last = pos_type(p_->end() - p_->begin());
                pos_type pos = 0;
                if (way == std::ios_base::cur) pos = pos_type(this->gptr() - p_->begin());
                else if (way == std::ios_base::end) pos = last;
                pos += pos_type(off);
                if (pos < 0 || pos > last) return pos_type(-1);
                this->setg(p_->begin(), p_->begin() + pos, p_->end());
                return pos;
            }
            return pos_type(-1);
        }
        
        virtual pos_type seekpos(pos_type pos, std::ios_base::openmode which = std::ios_base::in) {
            if (which & std::ios_base::in) {
                pos_type last = pos_type(p_->end() - p_->begin());
                if (pos < 0 || pos > last) return pos_type(-1);
                this->setg(p_->begin(), p_->begin() + pos, p_->end());
                return pos;
            }
            return pos_type(-1);
        }
        
        virtual std::streamsize showmanyc() {
            return std::streamsize(-1);
        }
        
    private:
        /* ----------------------------------------------------------------- */
        //  base_container
        /* ----------------------------------------------------------------- */
        class base_container {
        public:
            typedef CharT char_type;
            typedef CharT* iterator;
            
            base_container() {}
            virtual ~base_container() throw() {}
            
            virtual iterator begin() = 0;
            virtual iterator end() = 0;
        };
        
        /* ----------------------------------------------------------------- */
        //  data_container
        /* ----------------------------------------------------------------- */
        template <class T>
        class data_container : public base_container {
        public:
            typedef typename base_container::iterator iterator;
            
            data_container(const T& v) : v_(const_cast<T&>(v)) {}
            virtual ~data_container() throw() {}
            
            virtual iterator begin() { return reinterpret_cast<iterator>(&(*v_.begin())); }
            virtual iterator end() { return reinterpret_cast<iterator>(&(*v_.end())); }
            
        private:
            T& v_;
        };
        
        /* ----------------------------------------------------------------- */
        //  data_array
        /* ----------------------------------------------------------------- */
        template <class T>
        class data_array : public base_container {
        public:
            typedef typename base_container::iterator iterator;
            
            data_array(const T* v, size_type n) : v_(const_cast<T*>(v)), size_(n) {}
            virtual ~data_array() throw() {}
            
            virtual iterator begin() { return reinterpret_cast<iterator>(v_); }
            virtual iterator end() { return reinterpret_cast<iterator>(v_ + size_); }
            
        private:
            T* v_;
            size_type size_;
        };
        
        typedef shared_ptr<base_container> container_ptr;
        
        container_ptr p_;
    };
    
    /* --------------------------------------------------------------------- */
    //  basic_ivstream
    /* --------------------------------------------------------------------- */
    template <
        class CharT,
        class Traits = std::char_traits<CharT>
    >
    class basic_ivstream : public std::basic_istream<CharT, Traits> {
    public:
        typedef basic_ivstreambuf<CharT, Traits> streambuf_type;
        typedef typename streambuf_type::size_type size_type;
        
        template <class Container>
        basic_ivstream(const Container& data) :
            std::basic_istream<CharT, Traits>(0), buf_(data) {
            this->rdbuf(&buf_);
        }
        
        template <class Type>
        basic_ivstream(const Type* data, size_type n) :
            std::basic_istream<CharT, Traits>(0), buf_(data, n) {
            this->rdbuf(&buf_);
        }
        
        virtual ~basic_ivstream() throw() { this->rdbuf(0); }
        
    private:
        streambuf_type buf_;
    };
    
    typedef basic_ivstream<char> ivstream;
}

#endif // CLX_VSTREAM_H