Are there binary memory streams in C++

Viewed 67195

I usually use stringstream to write into in-memory string. Is there a way to write to a char buffer in binary mode? Consider the following code:

stringstream s;
s << 1 << 2 << 3;
const char* ch = s.str().c_str();

The memory at ch will look like this: 0x313233 - the ASCII codes of the characters 1, 2 and 3. I'm looking for a way to write the binary values themselves. That is, I want 0x010203 in the memory. The problem is that I want to be able to write a function

void f(ostream& os)
{
    os << 1 << 2 << 3;
}

And decide outside what kind of stream to use. Something like this:

mycharstream c;
c << 1 << 2 << 3; // c.data == 0x313233;
mybinstream b;
b << 1 << 2 << 3; // b.data == 0x010203;

Any ideas?

7 Answers

You can do this sort of thing with templates. E.g:

//struct to hold the value:
template<typename T> struct bits_t { T t; }; //no constructor necessary
//functions to infer type, construct bits_t with a member initialization list
//use a reference to avoid copying. The non-const version lets us extract too
template<typename T> bits_t<T&> bits(T &t) { return bits_t<T&>{t}; }
template<typename T> bits_t<const T&> bits(const T& t) { return bits_t<const T&>{t}; }
//insertion operator to call ::write() on whatever type of stream
template<typename S, typename T>
S& operator<<(S &s, bits_t<T> b) {
    return s.write((char*)&b.t, sizeof(T));
}
//extraction operator to call ::read(), require a non-const reference here
template<typename S, typename T>
S& operator>>(S& s, bits_t<T&> b) {
    return s.read((char*)&b.t, sizeof(T));
}

It could use some cleanup, but it's functional. E.g:

//writing
std::ofstream f = /*open a file*/;
int a = 5, b = -1, c = 123456;
f << bits(a) << bits(b) << bits(c);

//reading
std::ifstream f2 = /*open a file*/;
int a, b, c;
f >> bits(a) >> bits(b) >> bits(c);
#include <sstream>

class bostringstream {
public:
  bostringstream() : oss() {}

  template <typename T, typename std::enable_if<std::is_fundamental<T>::value,
                                                bool>::type = true>
  bostringstream& operator<<(const T& v) {
    oss.write((char*)&v, sizeof(T));
    return *this;
  }

  template <typename T, typename std::enable_if<
                            std::is_fundamental<typename T::value_type>::value,
                            bool>::type = true>
  bostringstream& operator<<(const T& v) {
    oss.write((char*)v.data(), v.size() * sizeof(typename T::value_type));
    return *this;
  }

  template <typename _InputIterator>
  bostringstream& write(_InputIterator first, _InputIterator last) {
    char* data = (char*)&(*first);
    auto n = std::distance(first, last);
    oss.write(data, n * sizeof(*first));
    return *this;
  }

  template <typename T, typename std::enable_if<std::is_fundamental<T>::value,
                                                bool>::type = true>
  bostringstream& write(const T* v, std::streamsize count) {
    oss.write((char*)v, sizeof(T) * count);
    return *this;
  }

  auto rdbuf() const { return oss.rdbuf(); }

  auto str() const { return oss.str(); }

  std::size_t size() { return oss.tellp(); }

protected:
  std::ostringstream oss;
};

Example:

#include <array>
#include <string>
#include <vector>
#include <iostream>
#include <fstream>
#include "bsstream.hpp"

int main(int argc, char **argv) {

  int i = 1;
  float j = 1.1;
  double k = 1.2;
  std::vector<int> ii{1,2};
  std::vector<double> jj{1.2,2.2};
  std::string kk = "abcd";
  std::array<int, 2> ll{3,4};
  int l[] = {1,2};

  bostringstream of;
  of << i << j <<k;
  of <<ii << jj << kk << ll;
  of.write(l, 2);

  std::ofstream oof("foo.bin", std::ios::binary);
  oof << of.str();
  oof.close();

}

Not an elegant solution but works and flexible

I really like Han Luo's approach and have verified it to work great! If changing the oss member variable to use std::stringstream (vs. ostringstream) this class could also be used for extraction with overloaded stream extraction operators like this:

    template <typename T, typename std::enable_if<std::is_fundamental<T>::value, bool>::type = true>
    bostringstream& operator>> (T& v)
    {
        char buffer[sizeof(T)];
        oss.read(buffer, sizeof(T));
        v = *(T*)buffer;
        return *this;
    }

The example templates support integral types and if adding a new template for std::is_compound it's possible to also support types like std::map. For things like std::vector, that are "is_fundemental" I'd suggest pushing the size to the stream first, so on the extraction side it can be pulled to know how many elements to pull afterwards. This approach can work with the common std::vector and std::map types quite nicely.

Related