That's very nice. It's good to remind people that binary IO does imply worrying about endian-ness.
I do have a few additional caveats and improvements to suggest.
1) On C++ basics, there is no reason to use pointers in this case, passing-by-reference would be cleaner and safer.
2) Your use of the std::ofstream and std::ifstream classes is a bit problematic. The first problem is that you obviously don't need the stream to be a file-stream, so you should use the more general classes of std::ostream and std::istream . The second problem is that your read/write functions are assuming that the stream in question is prepared for binary IO (i.e. opened with the ios::binary flag), this is the kind of "externalized responsibility" that can lead to robustness problems (i.e. you cannot guarantee that the execution of your read/write functions is predictable, because it depends on an external assumption). C++ solves this problem with classes that allow you to protect your invariants, and allow you to not make any assumptions that could be dangerous.
3) A simple issue is your use of sizeof(buffer.b[x]) , this is OK, but is also guaranteed by the C++ Standard to always output 1, so you don't need to use sizeof() there.
4) You should not rely on a in-code definition of your ENDIAN pre-processor flag. Most compilers have standard pre-processor flags to indicate the endian-ness of the environment for which the code is being compiled. For example, for compliance with both GCC and MSVC, you can do:
#ifdef __GNUC__
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define ENV_IS_LITTLE_ENDIAN
#endif
#endif
#ifdef _MSC_VER
//Microsoft works only for Windows and Windows is only little-endian:
#define ENV_IS_LITTLE_ENDIAN
#endif
5) The above leads me to the other issue, which is that the difference in endian-ness is known at compile-time, in fact, it is known at preprocessing-time, so you should use a pre-processor conditional to switch your implementations, not a regular conditional, for example:
#ifndef ENV_IS_LITTLE_ENDIAN
for (int x=sizeof(T)-1; x>=0; x--)
FILE->write(&(buffer.b[x]), sizeof(buffer.b[x]));
#else
for (int x=0; x<sizeof(T); x++)
FILE->write(&(buffer.b[x]), sizeof(buffer.b[x]));
#endif
6) You don't really need to name your union types.
7) You actually have an error in your code, that is, you give the "fileOut" object to the readObj function, that's obviously a simple mistake.
8) It is important to mention and deal with the fact that your read/write function can only work for built-in types. Of course, any non-POD class type will fail upon construction of the union, because non-POD types are not allowed inside a union (a POD type is a class or built-in type that has no constructor, no copy-constructor, no copy-assignment operator and no destructor). Basically, any non-trivial class type will not work with your read/write functions, and that's a good thing.
The real problem is that a POD type will work with your read/write functions even if it is more than just a built-in type (int, float, double, etc.). For example, a class like struct Vect2D { int x, y; }; is a POD-type, and thus, is allowed to be part of a union, so your read/write functions will work with it. BUT, you will screw up the whole endian-ness business with that, because using your write function on a big endian system and then reading the file back from a little endian system will result in an inversion of the (x,y) coordinates. So, you need to make sure that your functions are not usable with a type that is not a fundamental built-in type. It just so happens that you can achieve exactly this with Boost libraries:
#include <boost/type_traits.hpp>
#include <boost/utility.hpp>
template <class T>
typename boost::enable_if_c< boost::is_fundamental<T>::value,
void >::type writeObj(std::ostream& out, T obj) {
//..
};
template <class T>
typename boost::enable_if_c< boost::is_fundamental<T>::value,
void >::type readObj(std::istream& in, T& obj) {
//..
}; So, implementing all these recommendations, you get:
#include <fstream>
#ifdef __GNUC__
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define ENV_IS_LITTLE_ENDIAN
#endif
#endif
#ifdef _MSC_VER
//Microsoft works only for Windows and Windows is only little-endian:
#define ENV_IS_LITTLE_ENDIAN
#endif
class binary_file_writer {
private:
std::ofstream file_out;
public:
binary_file_writer(const char* aFilename) :
file_out(aFilename, std::ios::out | std::ios::binary) { };
template <typename T>
friend
boost::enable_if_c< boost::is_fundamental<T>::value,
binary_file_writer& operator << (binary_file_writer& lhs, T rhs) {
union {
char b[sizeof(T)];
T real;
} buffer;
buffer.real = rhs;
#ifndef ENV_IS_LITTLE_ENDIAN
for (int x = sizeof(T) - 1; x >= 0; --x)
file_out.write(&(buffer.b[x]), 1);
#else
for (int x = 0; x < sizeof(T); ++x)
file_out.write(&(buffer.b[x]), 1);
#endif
return *this;
};
};
class binary_file_reader {
private:
std::ifstream file_in;
public:
binary_file_reader(const char* aFilename) :
file_in(aFilename, std::ios::in | std::ios::binary) { };
template <typename T>
friend
boost::enable_if_c< boost::is_fundamental<T>::value,
binary_file_reader& operator << (binary_file_reader& lhs, T& rhs) {
union {
char b[sizeof(T)];
T real;
} buffer;
#ifndef ENV_IS_LITTLE_ENDIAN
for (int x = sizeof(T) - 1; x >= 0; --x)
file_in.read(&(buffer.b[x]), 1);
#else
for (int x = 0; x < sizeof(T); ++x)
file_in.read(&(buffer.b[x]), 1);
#endif
rhs = buffer.real;
return *this;
};
};
int main() {
int a = 1;
int a2;
char b = 3;
char b2;
float c = 45.2;
float c2;
{
binary_file_writer fileOut("Test.bin");
fileOut << a;
fileOut << b;
fileOut << c;
};
{
binary_file_reader fileIn("Test.bin");
fileIn >> a2;
fileIn >> b2;
fileIn >> c2;
};
std::cout << a2 << " " << b2 << " " << c2 << std::endl;
return 0;
};