Are there binary memory streams in C++

C++Iostream

C++ Problem Overview


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?

C++ Solutions


Solution 1 - C++

To read and write binary data to streams, including stringstreams, use the read() and write() member functions. So

unsigned char a(1), b(2), c(3), d(4);
std::stringstream s;
s.write(reinterpret_cast<const char*>(&a), sizeof(unsigned char));
s.write(reinterpret_cast<const char*>(&b), sizeof(unsigned char));
s.write(reinterpret_cast<const char*>(&c), sizeof(unsigned char));
s.write(reinterpret_cast<const char*>(&d), sizeof(unsigned char));
  
s.read(reinterpret_cast<char*>(&v), sizeof(unsigned int)); 
std::cout << std::hex << v << "\n";

This gives 0x4030201 on my system.

Edit: To make this work transparently with the insertion and extraction operators (<< and >>), your best bet it to create a derived streambuf that does the right thing, and pass that to whatever streams you want to use.

Solution 2 - C++

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);

Solution 3 - C++

Well, just use characters, not integers.

s << char(1) << char(2) << char(3);

Solution 4 - C++

overloading some unusual operators works rather well. Here below I choosed to overload <= because it has the same left-to-right associativity as << and has somehow a close look-and-feel ...

#include <iostream>
#include <stdint.h>
#include <arpa/inet.h>

using namespace std;

ostream & operator<= (ostream& cout, string const& s) {
    return cout.write (s.c_str(), s.size());
}
ostream & operator<= (ostream& cout, const char *s) {
    return cout << s;
}
ostream & operator<= (ostream&, int16_t const& i) {
    return cout.write ((const char *)&i, 2);
}
ostream & operator<= (ostream&, int32_t const& i) {
    return cout.write ((const char *)&i, 4);
}
ostream & operator<= (ostream&, uint16_t const& i) {
    return cout.write ((const char *)&i, 2);
}
ostream & operator<= (ostream&, uint32_t const& i) {
    return cout.write ((const char *)&i, 4);
}

int main() {
    string s("some binary data follow : ");

    cout <= s <= " (machine ordered) : " <= (uint32_t)0x31323334 <= "\n"
         <= s <= " (network ordered) : " <= htonl(0x31323334) ;
    cout << endl;

    return 0;
}

There are several drawbacks :

  • the new meaning of <= may confuse readers or lead to unexpected results :

     cout <= 31 <= 32;
    

    won't give the same result as

     cout <= (31 <= 32);
    
  • the endianess isn't clearly mentionned at reading the code, as illustrated in the above example.

  • it cannot mix simply with << because it doesn't belong to the same group of precedence. I usually use parenthesis to clarify such as :

     ( cout <= htonl(a) <= htonl(b) ) << endl;
    

Solution 5 - C++

For this use case I implemented myself a "raw shift operator":

template <typename T, class... StreamArgs>
inline std::basic_ostream<StreamArgs...> &
operator <= (std::basic_ostream<StreamArgs...> & out, T const & data) {
        out.write(reinterpret_cast<char const *>(&data), sizeof(T));
        return out;
}

Put it somewhere convenient and use it like this:

std::cout <= 1337 <= 1337ULL <= 1337. <= 1337.f;

Advantages:

  • chainable
  • automatic sizeof()
  • takes arrays and struct/class instances, too

Disadvantages:

  • unsafe for non-POD objects: leaks pointers and padding
  • output is platform specific: padding, endianess, integer types

Solution 6 - 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

Solution 7 - C++

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionFireAphisView Question on Stackoverflow
Solution 1 - C++KeithBView Answer on Stackoverflow
Solution 2 - C++Samuel PowellView Answer on Stackoverflow
Solution 3 - C++Lukáš LalinskýView Answer on Stackoverflow
Solution 4 - C++Jean Daniel PaugetView Answer on Stackoverflow
Solution 5 - C++kamikazeView Answer on Stackoverflow
Solution 6 - C++Han LuoView Answer on Stackoverflow
Solution 7 - C++James KillianView Answer on Stackoverflow