In a c++ textbook I learn that I should be able to do this (write/read a user-defined type to a file, in binary mode):
struct mystruct
{
int a;
double b;
};
void main()
{
struct mystruct s = {1,2.0};
std::ofstream out("binary.dat", std::ios::out | std::ios::binary);
out.write(reinterpret_cast< const char* > (&s), sizeof(struct mystruct));
}
The code compiles and runs but the resultant file is cannot be read by text-editors. then I use binary mode to read the file:
std::ifstream in("binary.dat", std::ios::in | std::ios::binary);
struct mystruct t;
in.read( reinterpret_cast<char*> (&t), sizeof(struct mystruct));
However it turns out it's unsuccessful (it doesn't get the numbers 1 and 2.0). Maybe I interpret the textbook wrongly..., or maybe something wrong with the code?
but the resultant file is cannot be read by text-editors
That's because you didn't write text to the file. The data cannot be interpreted meaningfully in any character encoding. You can use hexdump or similar tool for a meaningful representation.
Maybe I interpret the textbook wrongly..., or maybe something wrong with the code?
I don't think so. There's nothing wrong with the pieces of code individually except weird formatting and redundant keywords. Besides non-portability of the file of course. And the invalid return type of main.
However, perhaps what you did was a single program like this:
// ...
std::ofstream out("binary.dat", std::ios::binary);
out.write(reinterpret_cast<const char*>(&s), sizeof(s));
std::ifstream in("binary.dat", std::ios::binary);
in.read(reinterpret_cast<char*>(&t), sizeof(t));
// ...
There's a bug here. As you can see from the documentation, ostream::write writes to it's associated stream buffer. It's not until ostream::flush is called that the buffer is written to output. So, in the above code, the file is empty at the time it is read. This can obviously be fixed by calling out.flush() before reading the file.
As mentioned in comments, this is not at all portable. What that means is that a file written by your program on one computer may not be readable by your program on another computer. Even on the same computer, two programs compiled with different compiler options may not have the same memory layout for the class.
Why not do the standard way. It's simple and easy to understand
struct mystruct {
int a;
double b;
friend std::ostream & operator <<(std::ostream & out, mystruct & mys) {
out << mys.a << " " << mys.b << std::endl;
return out;
}
friend std::istream & operator >>(std::istream & in, mystruct & mys) {
in >> mys.a >> mys.b;
return in;
}
};
The usage will be simple now. For example
mystruct s;
To read
std::ifstream in("binary.dat");
in >> s;
To write
std::ofstream out("binary.dat");
out << s;
You can also overload std::string operator inside the struct if you want to serialize
operator std::string() const {
std::stringstream ss;
ss << a << " " << b;
return ss.str();
}
Related
The following piece of code produces a file with a single byte when compiled in MSVC, but when compiled by GCC 8.3 it produces an empty file.
std::wofstream out("tmpUTF16BrokenBOMFile.txt", std::ios_base::binary);
const wchar_t c = 0xFE;
// Both methods dont work:
// out.write(&c, 1);
out << c;
out.flush();
out.close();
std::cout << strerror(errno) << std::endl; // results with "Invalid or incomplete multibyte or wide character" on GCC
Is there any reliable way to output wchar_t into a binary stream without worrying about codecvt that can suddenly kick in and break everything?
I mean I am using binary mode exactly because I don't need codecvt here.
I know that its better to use ofstream for binary output, but unfortunately usage of wofstream is part of API that I cannot change (Although I see that it is an incredibly stupid idea, at least because wchar_t size is not defined by the standard).
Update:
looks like the following custom codecvt helps if I imbue it into the stream,
but I am not sure if this is the right way:
class noconvCodecvt : public std::codecvt<wchar_t, char, mbstate_t>
{
public:
virtual bool do_always_noconv() const throw() override
{
return true;
}
};
I have a little problem, I want to write something like that 0xff to a file, and I have done it:
ofstream file;
file.open(this->dbFile, ios::app | ios::binary);
const char prefix = 0xff;
file.write(&prefix, sizeof(char));
The previous example works well, but this is not my issue.
I want to write the previous example with writing the hexadecimal literally like the following:
ofstream file;
file.open(this->dbFile, ios::app | ios::binary);
file.write((const char*)0xff, sizeof(char));
But unfortunately, there is a runtime error occurs with the second example.
I know there's something wrong with the data conversion, but what is it?
first char is often used as a synonym for the non existing type byte, however you could easily make your own using statement like
#include <cstdint>
using byte_t = int8_t;
Consider this line file.write((const char*)0xff, sizeof(char)); as ostream::Write(const byte_t* pointerToArrayOfData, std::streamsize sizeOfData). 0xFF is the data you want to write but not an address of an array holding your data. sizeof(char)=sizeof(byte_t)=1 can be neglected, the method is designed to write single bytes. However you can use this to serialize custom types like
struct Point { int8_t x, y; };
array<Point, 4> myRectangle {/* */};
file.write(&myRectangle, sizeof(Point) * array.size());
file.write(&myRectangle, sizeof(array<Point, 4>));
Note this is platform dependent and you should always consider the generated file can only be read on the same system you created it. If you want to transfer binary data please consider some abstractions like protobuf.
TLDR;
using byte_t = char;
void write_binary_data(std::ostream& ostream, byte_t* data, size_t count)
{
ostream.write(data, count);
}
using binary_data_t = std::vector<byte_t>;
void write_binary_data(std::ostream& ostream, const binary_data_t data)
{
write_binary_data(ostream, data.data(), data.size());
}
ofstream file;
file.open(this->dbFile, ios::app | ios::binary);
write_binary_data(file, { 0xFF, 0x00, 0xFF });
In the main function, there are various vectors of different template types(float, int, char*). This function is called to read in formatted input from different files to fill each vector. My problem is coming from the type conversion since
v.push_back((T)(pChar));
does not like converting char* to float(presumably because of the decimal point).
Question: Is there a way to get correct conversions regardless of the data type as long as the input file is appropriate? (I've considered typeid(); but am not sold on using it)
template <class T>
void get_list(vector <T> & v, const char * path)
{
fstream file;
const char delim[1]{' '};
char line[512];
char * pChar;
file.open(path, ios_base::in);
if (file.is_open())
{
while (!file.eof())
{
file.getline(line, 512);
pChar = strtok(line, delim);
while (pChar != NULL)
{
v.push_back(pChar);
pChar = strtok(NULL, delim);
}
}
file.close();
}
else
{
cout << "An error has occurred while opening the specified file." << endl;
}
}
This is homework but this problem does not pertain directly to the objective of the assignment.
The assignment is on heaps for data structs/algorithm class.
Indeed, you can't simply cast a string to an arbitrary type, you'll need some code to parse and interpret the contents of the string. The I/O library has string streams for that:
std::stringstream ss(pChar);
T value;
ss >> value;
v.push_back(value);
This will work for all types that have a >> overload, including all built-in numeric types like float.
Alternatively, you might want to get rid of the nasty C-style tokenisation:
T value;
while (file >> value) {
v.push_back(value);
}
or
std::copy(
std::istream_iterator<T>(file),
std::istream_iterator<T>(),
std::back_inserter(v));
At the very least, change the loop to
while (file.getline(line, 512))
checking the file status after reading the line, so you don't process the final line twice.
I am new to c++ and specifically file handling.
I made this code.
#include <iostream>
#include <fstream>
using namespace std;
class student
{
public:
char s;
int age;
};
int main (void)
{
student a;
a.s = 'a';
a.age = 1;
ofstream myfile;
myfile.open("a1.txt",ios::app);
myfile.write(reinterpret_cast<char*>(&a),sizeof(student));
return 0;
}
When I opened the file a1.txt, it had the character a correctly in there, but for the integer it had some weird encoding and a message that if you continue to edit this file, it will be corrupted. Can't I write an object to a file containing an integer and a character or a string as well?
You're writing the binary representation of student into the file. The character will come out as expected; but the int will be the bytes used to represent the value, not a readable number.
If you want the output to be formatted as readable text, use formatted output:
myfile << a.s << ' ' << a.age << '\n';
For convenience, you could overload the operator for your class:
ostream & operator<<(ostream & os, student const & a) {
return os << a.s << ' ' << a.age;
}
myfile << a << '\n';
For more complex structures, you might consider the Boost.Serialization library. Or you might do what I tend to do, with tuples instead of plain structures, and variadic templates to read and write them, but that might be rather more fiddly than you'd like.
I would define your own << operator that handles your custom type.
#include <iostream>
#include <fstream>
using namespace std;
class student
{
char s;
int age;
};
ostream& operator<<(ostream &output, const student &o)
{
output << o.s << " " << o.age;
return output;
}
int main (void)
{
student a;
a.s = 'a';
a.age = 1;
ofstream myfile;
myfile.open("a1.txt",ofstream::app);
ofstream << a;
return 0;
}
When you called myfile.write(reinterpret_cast<char*>(&a),sizeof(student)); it was not converting your struct to a human-readable string then writing it to file. In reality, it was interpreting the memory of your struct as a series of characters then writing it to file.
You actually did write the int to the file, but not readable (123 => '1' '2' '3')
but the 4 (or 8) byte of that int. Your program can read the value back too,
the only probem is that we humans can´t read that form well.
Concering Strings:Just writing the whole struct will probably fail
(depending on the exact type of the string variable etc.), because the
"string" often stores only a pointer in the struct (which points to some
memory elsewhere, and this other memory isn´t written automatically to the file)
To be safe, write each variable of the struct explicitely (and handle different
var type appropiately) instead of writing them all together.
This way, things like different variable ordering and struct padding
can´t cause problems too. Other pitfalls to remember are different int
sizes and endianess on different computers... serialization isn´t trivial.
I have my struct:
struct a
{
int x;
float f;
double d;
char c;
char s[50];
};
and I wish append each time into my timer schedule into a binary file.
// declaration
std::ofstream outFile;
// constructor:
outFile.open( "save.dat", ios::app );
// tick:
outFile << a << endl;
but inside the save.dat appears only this:
0C3A0000..0C3A0000..0C3A0000..0C3A0000..0C3A0000..0C3A0000..0C3A0000..0C3A0000..0C3A0000..
thanks in advance
What you're currently doing is writing the address of the struct definition.
What you want to do is use ostream::write
outfile.write(reinterpret_cast<char*>(&myStruct), sizeof(a));
This will work as long as your struct is a POD (Plain Old Data) type (which your example is). POD type means that all members are of fixed size.
If you on the other hand have variable sized members then you would need to write out each member one by one.
A sensible way to serialize custom objects is to overload your own output stream operator:
std::ostream & operator<<(std::ostream & o, const a & x)
{
o.write(reinterpret_cast<char*>(&x.x), sizeof(int));
o.write(reinterpret_cast<char*>(&x.f), sizeof(float));
/* ... */
return o;
}
a x;
std::ofstream ofile("myfile.bin", std::ios::binary | std::ios::app);
ofile << a;
This is still platform-dependent, so to be a bit safer, you should probably use fixed-width data types like int32_t etc.
It might also not be the best idea semantically to use << for binary output, since it's often used for formatted output. Perhaps a slightly safer method would be to write a function void serialize(const a &, std::ostream &);