Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does ostream::write() require ‘const char_type*’ instead of ‘const void*’ in C++?

Tags:

The fwrite() function in C uses const void *restrict buffer as the first argument, so you can pass pointer to your struct as the first parameter directly.
http://en.cppreference.com/w/c/io/fwrite
e.g. fwrite(&someStruct, sizeof(someStruct), 1, file);

But in C++, the ostream::write() requires const char_type*, which forces you to use reinterpret_cast. (In Visual Studio 2013, it's const char*.)
http://en.cppreference.com/w/cpp/io/basic_ostream/write
e.g. file.write(reinterpret_cast<char*>(&someStruct), sizeof(someStruct));

In almost all cases, the binary data to be written to files is not a char array, so why does the standard prefer the style which seems more complex?

P.S.
1. Actually I used the write() method in ofstream with ios::binary mode, but according to the reference, it inherits ofstream. So I use ostream::write() above.
2. If you want to print a stream of characters, you could use operator<<(). Isn't write() method designed for writing raw data?
3. If write() is not the way to write binary data, then what is the way to do it within the standard? (Although this may bother portability of the code due to various memory align strategies on different platforms)

like image 1000
Mr. Ree Avatar asked Feb 17 '15 09:02

Mr. Ree


2 Answers

The portrayal of this as a C vs C++ thing is misleading. C++ provides std::fwrite(const void*, ...) just like C. Where C++ chooses to be more defensive is specifically the std::iostream versions.

"Almost in all cases the binary data to be written to files is not char array"

That's debatable. In C++ isn't not unusual to add a level of indirection in I/O, so objects are streamed or serialised to a convenient - and possibly portable (e.g. endian-standardised, without or with standardised structure padding) - representation, then deserialised/parsed when re-read. The logic is typically localised with the individual objects involved, such that a top-level object doesn't need to know details of the memory layout of its members. Serialisation and streaming tends to be thought of / buffered etc. at the byte level - fitting in better with character buffers, and read() and write() return a number of characters that could currently be transmitted - again at the character and not object level - so it's not very productive to pretend otherwise or you'll have a mess resuming partially successful I/O operations.

Raw binary writes / reads done naively are a bit dangerous as they don't handle these issues so it's probably a good thing that the use of these functions is made slightly harder, with reinterpret_cast<> being a bit of a code smell / warning.

That said, one unfortunate aspect of the C++ use of char* is that it may encourage some programmers to first read to a character array, then use inappropriate casts to "reinterpret" the data on the fly - like an int* aimed at the character buffer in a way that may not be appropriately aligned.

If you want to print a stream of characters, you could use operator<<(). Isn't write() method designed for writing raw data?

To print a stream of characters with operator<<() is problematic, as the only relevant overload takes a const char* and expects a '\0'/NUL-terminated buffer. That makes it useless if you want to print one or more NULs in the output. Further, when starting with a longer character buffer operator<< would often be clumsy, verbose and error prone, needing a NUL swapped in and back around the streaming, and would sometimes be a significant performance and/or memory use issue e.g. when writing some - but not the end - of a long string literal into which you can't swap a NUL, or when the character buffer may be being read from other threads that shouldn't see the NUL.

The provided std::ostream::write(p, n) function avoids these problems, letting you specify exactly how much you want printed.

like image 152
Tony Delroy Avatar answered Sep 19 '22 18:09

Tony Delroy


char_type is not exactly char *, it's the template parameter of the stream that represents the stream's character type:

template<typename _CharT, typename _Traits>
class basic_ostream : virtual public basic_ios<_CharT, _Traits>
{
public:
    // Types (inherited from basic_ios):
    typedef _CharT                  char_type;
    <...>

And std::ostream is just the char instantiation:

typedef basic_ostream<char> ostream;
like image 29
SingerOfTheFall Avatar answered Sep 19 '22 18:09

SingerOfTheFall