3

Writing binary data to a file in C is simple: use fwrite, passing the address of the object you want to write and the size of the object. Is there something more "correct" for Modern C++ or should I stick to using FILE* objects? As far as I can tell the IOStream library is for writing formatted data rather than binary data, and the write member asks for a char* leaving me littering my code with casts.

hatcat
  • 1,876
  • 1
  • 18
  • 37
  • 3
    You use the [`std::basic_ostream::write`](http://en.cppreference.com/w/cpp/io/basic_ostream/write) and [`std::basic_istream::read`](http://en.cppreference.com/w/cpp/io/basic_istream/read) functions to write or read e.g. structures or other binary/raw data. And yes, you have to do type-casting to make it work. – Some programmer dude Feb 26 '15 at 14:49
  • @JoachimPileborg this should be an answer. – n. m. could be an AI Feb 26 '15 at 14:52
  • These casts are a friendly reminder that *you are reinterpreting your data as a sequence of bytes*. If you don't like the litter, wrap `read` and `write` such that te wrappers accept `void*` rather than `char*`, and add error handling while you're at it. – n. m. could be an AI Feb 26 '15 at 14:54
  • http://stackoverflow.com/questions/7349689/c-how-to-print-using-cout-the-way-a-number-is-stored-in-memory – Steephen Feb 26 '15 at 15:00
  • See [this mine answer](http://stackoverflow.com/a/28748914/841108) to a related question. – Basile Starynkevitch Feb 26 '15 at 21:11

2 Answers2

4

So the game here is to enable argument dependent lookup on reading and writing, and make sure you don't try to read/write things that are not flat data.

It fails to catch data containing pointers, which also should not be read/written this way, but it is better than nothing

namespace serialize {
  namespace details {
    template<class T>
    bool write( std::streambuf& buf, const T& val ) {
      static_assert( std::is_standard_layout<T>{}, "data is not standard layout" );
      auto bytes = sizeof(T);
      return buf.sputn(reinterpret_cast<const char*>(&val), bytes) == bytes;
    }
    template<class T>
    bool read( std::streambuf& buf, T& val ) {
      static_assert( std::is_standard_layout<T>{}, "data is not standard layout" );
      auto bytes = sizeof(T);
      return buf.sgetn(reinterpret_cast<char*>(&val), bytes) == bytes;
    }
  }
  template<class T>
  bool read( std::streambuf& buf, T& val ) {
    using details::read; // enable ADL
    return read(buf, val);
  }
  template<class T>
  bool write( std::streambuf& buf, T const& val ) {
    using details::write; // enable ADL
    return write(buf, val);
  }
}

namespace baz {
    // plain old data:
    struct foo {int x;};
    // not standard layout:
    struct bar {
      bar():x(3) {}
      operator int()const{return x;}
      void setx(int s){x=s;}
      int y = 1;
    private:
      int x;
    };
    // adl based read/write overloads:
    bool write( std::streambuf& buf, bar const& b ) {
        bool worked = serialize::write( buf, (int)b );
        worked = serialize::write( buf, b.y ) && worked;
        return worked;
    }
    bool read( std::streambuf& buf, bar& b ) {
        int x;
        bool worked = serialize::read( buf, x );
        if (worked) b.setx(x);
        worked = serialize::read( buf, b.y ) && worked;
        return worked;
    }
}

I hope you get the idea.

live example.

Possibly you should restrict said writing based off is_pod not standard layout, with the idea that if something special should happen on construction/destruction, maybe you shouldn't be binary blitting the type.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • That's looking good, but before I accept, can you tell me why you have declared a forwarding function? – hatcat Feb 28 '15 at 11:33
  • @hatcat easy ADL. Call `serialize::read` explicitly and it will still find a local namespace `read` override for a type. Easier than forcing end users to always be ADL friendly when calling `read`, so I did the work for them. – Yakk - Adam Nevraumont Feb 28 '15 at 11:44
  • @hatcat as an aside, have a not standard layout sfinae tested `std::tuple` overload in details. Then aggregates can `read(stream,std::tie(my_members...))` and poof, done. – Yakk - Adam Nevraumont Feb 28 '15 at 16:20
2

Since you are already bypassing all formatting, I would recommend using the std::filebuf class directly to avoid possible overheads from std::fstream; it's definitely better than FILE* due to RAII.

You can't escape from the casts this way, sadly. But it's not hard to wrap it, like:

template<class T>
void write(std::streambuf& buf, const T& val)
{
    std::size_t to_write = sizeof val;
    if (buf.sputn(reinterpret_cast<const char*>(&val), to_write) != to_write)
        // do some error handling here
}
DanielKO
  • 4,422
  • 19
  • 29