Search code examples
c++c++11booststdboost-iostreams

Create class using internal vector member as the container for stream access to class instances


Appreciation in advance for the time of anyone who is willing to look at this.

I'd like to make a simple class that allows all stream interfaces, but only reads/writes to a simple std::vector stored in the class. It seems to me after trying to re-write everything myself, then trying to derive from basic_stream, that using boost::iostreams minimizes the amount of code I will need to re-write. For example: this is what I want to do, but I want my class to be used like os in there (thus why I try to derive from boost::iostreams::stream): http://theboostcpplibraries.com/boost.iostreams-devices

Here is a "first try", in which I try to inherit from stream and stream_buffer (don't know if necessary). All I want is for stream operators to all use the std::vector<char> data as the container.

//File: memfile2.h
#pragma once

#include <algorithm>                       // copy, min
#include <iosfwd>                          // streamsize
#include <boost/iostreams/categories.hpp>  // source_tag
#include <boost/iostreams/device/array.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/stream_buffer.hpp>

//REV: use boost iostreams to let user write to a local vector of chars
//as a memory file.

//REV: Or just "get" one from the pointer, i.e. have a mem_ptr which "opens" a file.


struct mfile : public boost::iostreams::stream<boost::iostreams::array_source>, boost::iostreams::stream_buffer
{
  std::vector<char> data;

 mfile()
   : boost::iostreams::stream<boost::iostreams::array_source>( data ),
    boost::iostreams::stream_buffer()
    {
    }

  void other_funct()
  {
  }
};

An example use program would be:

#include <memfile2.h>

int main()
{
  mfile f;

  f << "YOLO";

  std::string fromf;
  //f.seekg(0, BOOST_IOS::beg);
  f >> fromf;
  fprintf(stdout, "OUTPUT: [%s]\n", fromf.c_str());

  f.other_funct();
}

Solution

  • Here are three takes:

    Inheritance, pretty generic

    Live On Coliru

    #include <boost/iostreams/stream.hpp>
    #include <boost/iostreams/device/back_inserter.hpp>
    #include <iostream>
    #include <iomanip>
    #include <fstream>
    
    template <typename CharT = char, typename CharTraits = std::char_traits<CharT>,
             typename Buffer = std::vector<CharT>,
             typename Base   = boost::iostreams::stream<boost::iostreams::back_insert_device<Buffer> > 
         >
    struct basic_fixed_stream : private Buffer, public Base {
        basic_fixed_stream() : Buffer(), Base(*static_cast<Buffer*>(this)) {}
    
        std::string to_string() const {
            flush(*this);
            return { Buffer::begin(), Buffer::end() };
        }
    };
    
    using fixed_stream = basic_fixed_stream<char>;
    
    int main()
    {
    
        fixed_stream f;
        f << "YOLO " << std::showbase << std::hex << std::setfill('0') << 42;
    
        std::string fromf = f.to_string();
        fprintf(stdout, "OUTPUT: [%s]\n", fromf.c_str());
    }
    

    Prints:

    OUTPUT: [YOLO 0x2a]
    

    Simpler, no inheritance

    Live On Coliru

    #include <boost/iostreams/stream.hpp>
    #include <boost/iostreams/device/back_inserter.hpp>
    #include <iostream>
    #include <iomanip>
    #include <fstream>
    
    struct fixed_stream {
        template <typename OS=std::ostream> friend fixed_stream& operator<<(fixed_stream& os, OS&(*manip)(OS&)) {
            os._stream << manip;
            return os;
        }
    
        template <typename T> friend fixed_stream& operator<<(fixed_stream& os, T const& v) {
            os._stream << v;
            return os;
        }
        std::string to_string() const {
            flush(_stream);
            return { _buffer.begin(), _buffer.end() };
        }
    
        operator std::ostream&() { return _stream; }
      private:
        using buffer_t = std::vector<char>;
        buffer_t _buffer;
        boost::iostreams::stream<boost::iostreams::back_insert_device<buffer_t> > _stream { _buffer };
    };
    
    int main()
    {
        fixed_stream f;
        f << "YOLO " << std::showbase << std::hex << std::setfill('0') << 42;
    
        std::string fromf = f.to_string();
        fprintf(stdout, "OUTPUT: [%s]\n", fromf.c_str());
    }
    

    With the same output:

    OUTPUT: [YOLO 0x2a]
    

    Bidirectional:

    Another take with istream functionality too. Note this fixes the capacity (for convenience):

    Note, if you push more than capacity input, the stream state goes bad. You will want to handle errors and/or clear() the state.

    Live On Coliru

    #include <boost/iostreams/stream.hpp>
    #include <boost/iostreams/device/array.hpp>
    #include <iostream>
    #include <iomanip>
    #include <fstream>
    
    template <typename CharT = char, typename CharTraits = std::char_traits<CharT>,
             typename Buffer = std::vector<CharT>,
             typename Base   = boost::iostreams::stream<boost::iostreams::array> 
         >
    struct basic_fixed_stream : private Buffer, public Base {
        basic_fixed_stream(size_t capacity = 1024) : Buffer(capacity), Base(this->data(), this->size()) {}
    
        using Base::clear;
    
        std::string to_string() const {
            flush(*this);
            return { Buffer::begin(), Buffer::end() };
        }
    };
    
    using fixed_stream = basic_fixed_stream<char>;
    
    int main()
    {
        {
            fixed_stream f;
            f << "YOLO " << std::showbase << std::hex << std::setfill('0') << 42;
    
            std::string fromf = f.to_string();
            fprintf(stdout, "OUTPUT: [%s]\n", fromf.c_str());
        }
    
        {
            fixed_stream f;
            {
                std::ifstream ifs("main.cpp");
                f << ifs.rdbuf();
            }
            f.clear();
            f.seekg(0);
    
            std::string line;
            while (getline(f, line))
                fprintf(stdout, "OUTPUT: [%s]\n", line.c_str());
        }
    
    
    }
    

    Output:

    OUTPUT: [YOLO 0x2a]
    OUTPUT: [#include <iostream>]
    OUTPUT: [#include <boost/spirit/home/x3.hpp>]
    OUTPUT: [#include <boost/fusion/adapted/std_tuple.hpp>]
    OUTPUT: [#include <boost/spirit/home/x3/binary.hpp>]
    OUTPUT: []
    OUTPUT: [namespace x3 = boost::spirit::x3;]
    OUTPUT: []
    OUTPUT: [namespace hessian {]
    OUTPUT: []
    OUTPUT: [    typedef std::string string_t;]
    OUTPUT: []
    OUTPUT: [    namespace parser {]
    OUTPUT: []
    OUTPUT: [        struct bstring : x3::parser<bstring> {]
    OUTPUT: [            using attribute_type = hessian::string_t;]
    OUTPUT: []
    OUTPUT: [            // string ::= s b1 b0 <utf8-data> string]
    OUTPUT: [            //       ::= S b1 b0 <utf8-data>]
    OUTPUT: [            //       ::= [x00-x1f] <utf8-data>]
    OUTPUT: [            // NOTE: The length means number of UTF16 characters but the content is given in UTF8 characters!]
    OUTPUT: [            template <typename It, typename Ctx, typename Attr>]
    OUTPUT: [                bool parse(It& f, It const& l, Ctx&, x3::unused_type, Attr& attr) const {]
    OUTPUT: [                    auto saved = f;]
    OUTPUT: [                    char type;]
    OUTPUT: [                    size_t len;]
    OUTPUT: [                    auto tied = std::tie(type, len);]
    OUTPUT: []
    OUTPUT: [                    while (x3::parse(f,l,x3::char_("sS") >> x3::big_word,tied)) {]
    OUTPUT: [                 ]
    

    You'll notice that's the first kilobyte of the source code!