These methods are supposed to save and load the entirety of the object they're associated with. When I compile the program under Linux through gcc, the save seems to work but it segfaults when loading. When I compile it under Windows through the Visual Studio compiler, it works like a dream. I am not sure what the differences are, but I've got a hunch that it involves some gcc oddity.
The two methods:
void User::SaveToFile()
{
ofstream outFile;
string datafile_name = username + "_data";
outFile.open(datafile_name.c_str(), ios::binary);
outFile.write((char*)this, sizeof(*this));
}
void User::LoadFromFile(string filename)
{
ifstream inFile;
inFile.open(filename.c_str(), ios::binary);
inFile.read((char*)this, sizeof(*this));
}
The declaration:
class User
{
private:
string username;
string realname;
string password;
string hint;
double gpa;
vector<Course> courses;
public:
double PredictGPA();
void ChangePassword();
void SaveToFile();
void LoadFromFile(string filename);
void SetUsername(string _username){username = _username;}
string GetUsername(){return username;}
void SetRealname(string _realname){realname = _realname;}
string GetRealname(){return realname;}
void SetPass(string _password){password = _password;}
string GetPass(){return password;}
void SetHint(string _hint){hint = _hint;}
string GetHint(){return hint;}
};
Your class User is not a POD type, its not a Plain Old Data type (as C structs are). You cannot just read and write its memory bitwise and expect it to work. Both string and vector are not PODs, they keep pointers to their dynamically allocated data. When reading those back, attempts to access invalid memory will result in a segfault. What's more, the contents of both the string and vector are not actually being saved at all, since they are not within the memory layout of the object (it may work sometimes with string with SBO, but its just but chance and still undefined to do it).
You would need a way to serialize and deserialize your class; your class can't magically become an object when you read it in like that.
Instead you would need to supply to functions that you call when loading/saving your class that store the class in some format of your choosing e.g. XML.
so instead of
outFile.write((char*)this, sizeof(*this));
have some member function to convert it to a string with some format that you easily can parse when you load it (or some binary format whatever you find easier), then save it.
outFile.write(this->myserialize(), mysize);
You can't write into string like that. For one thing it usually stores its data dynamically, i.e. not inside the object at all, and for another you shall not rely on any particular layout of it.
There are similar issues with vectors, and you don't appear to have considered endianness and padding at all.
Put simply, you're making assumptions that do not hold.
In general, do not mess with complex (non-POD) objects on the byte level. Serialise with some text format instead, using the objects' public member functions to extract and restore their state.
Have you considered JSON?
Things like strings etc may contain pointers - in which case your method can go horribly wrong.
You need to serialise the data - I.e. convert it to a series of bytes.
Then when reading the data you just read the bytes and then create the object from that. The new pointers will be correct.
If you stay with this route I would write the length of the string instead of null terminating it. Easier to allocated on loading. There is alot to consider in a binary format. Each field should have some type of ID so it can be found if in wrong spot or a different version of your program. Also at the beginning of your file write what endianess you are using and the size of your integers etc. Or decide a standard size and endianess for everything. I use to write code like this all the time for networking and file storage. There are much better modern approaches. Also consider using a buffer and creating Serialize() function.
Good modern alternatives include :SQLite3, XML, JSON
Untested Example:
class object
{
Load()
{
ifstream inFile;
int size;
inFile.open("filename", ios::binary);
inFile.read(&size, 4);
stringA.resize(size);
inFile.read(&stringA[0], size);
inFile.read(&size, 4);
stringB.resize(size);
inFile.read(&stringB[0], size);
inFile.close(); //don't forget to close your files
}
Save()
{
ofstream outFile;
int size;
outFile.open("filename", ios::binary);
size = stringA.size();
outFile.write(&size, 4);
outFile.write(&stringA[0], size);
size = stringB.size();
outFile.write(&size, 4);
outFile.write(&stringA[0], size);
outFile.close();
}
private:
std::string stringA
std::string stringB
};
Related
first sorry for my bad english. i am just joined this forum and search for
how to correctly write vector to binary file. i just got from this forum an answer like this(i have modified it a little):
#include <iostream>
#include <string.h>
#include <vector>
#include <fstream>
using namespace std;
class Student
{
public:
char m_name[30];
int m_score;
public:
Student()
{
}
Student(const char name[], const int &score)
:m_score(score)
{
strcpy(m_name, name);
}
void print() const
{
cout.setf(ios::left);
cout.width(20);
cout << m_name << " " << m_score << endl;
}
};
int main()
{
vector<Student> student;
student.push_back(Student("Alex",19));
student.push_back(Student("Maria",20));
student.push_back(Student("muhamed",20));
student.push_back(Student("Jeniffer",20));
student.push_back(Student("Alex",20));
student.push_back(Student("Maria",21));
ofstream fout("data.dat", ios::out | ios::binary);
fout.write((char*) &student, sizeof(student));
fout.close();
vector<Student> student2;
ifstream fin("data.dat", ios::in | ios::binary);
fin.seekg(0, ifstream::end);
int size = fin.tellg() / sizeof (student2);
student2.resize(size);
fin.seekg(0, ifstream::beg);
fin.read((char*)&student2, sizeof(student2));
vector<Student>::const_iterator itr = student2.begin();
while(itr != student2.end())
{
itr->print();
++itr;
}
fin.close();
return 0;
}
but when i run it. on my linux mint i got this result:
Alex 19
Maria 20
muhamed 20
Jeniffer 20
Alex 20
Maria 21
*** glibc detected *** ./from vector to binary: corrupted double-linked list: 0x0000000000633030 ***
i am new to c++.
some one please help me, been stuck in this problem last 2 weeks.
thanks in advance for the answer.
You are writing to file the vector structure, not its data buffer. Try change writing procedure to:
ofstream fout("data.dat", ios::out | ios::binary);
fout.write((char*)&student[0], student.size() * sizeof(Student));
fout.close();
And instead of calculation size of vector from file size, it's better write vector size (number of objects) before. In the case you can write to the same file other data.
size_t size = student.size();
fout.write((char*)&size, sizeof(size));
To store a vector<T> of PODs in a file, you have to write the contents of the vector, not the vector itself. You can access the raw data with &vector[0], address of the first element (given it contains at least one element). To get the raw data length, multiply the number of elements in the vector with the size of one element:
strm.write(reinterpret_cast<const char*>(&vec[0]), vec.size()*sizeof(T));
The same applies when you read the vector from the file; The element count is the total file size divided by the size of one element
(given that you only store one type of POD in the file):
const size_t count = filesize / sizeof(T);
std::vector<T> vec(count);
strm.read(reinterpret_cast<char*>(&vec[0]), count*sizeof(T));
This only works if you can calculate the number of elements based on the file size (if you only store one type of POD or if all vectors contain the same number of elements). If you have vectors with different PODs with different lengths, you have to write the number of elements in the vector to the file before writing the raw data.
Furthermore, when you transfer numeric types in binary form between different systems, be aware of endianness.
For functions read() and write(), you need what is called "plain old data" or "POD". That means basically that the class or structure must have no pointers inside them, and no virtual functions. the implementation of vector certainly has pointers - I'm not sure about virtual functions.
You will have to write a function that stores a student at a time (or that translates a bunch of students to a array [not a vector] of bytes or some such - but that's more complex).
The reason you can't write non-POD data, in particular pointers, to a binary file is that when you read the data back again, you can almost certainly bet that the memory layout has changed from when you wrote it. It becomes a little bit like trying to park in the same parking space at the shops - someone else will have parked in the third spot from the entrance when you turn up next time, so you'll have to pick another spot. Think of the memory allocated by the compiler as parking spaces, and the student information as cars.
[Technically, in this case, it's even worse - your vector doesn't actually contain the students inside the class, which is what you are writing to the file, so you haven't even saved the information about the students, just the information about where they are located (the number of the parking spaces)]
You probably cannot write in binary (the way you are doing) any std::vector because that template contains internal pointers, and writing and re-reading them is meaningless.
Some general advices:
don't write in binary any STL template containers (like std::vector or std::map), they surely contain internal pointers that you really don't want to write as is. If you really need to write them, implement your own writing and reading routines (e.g. using STL iterators).
avoid using strcpy without care. Your code will crash if the name has more than 30 characters. At least, use strncpy(m_name, name, sizeof(m_name)); (but even that would work badly for a 30 characters name). Actually, m_name should be a std::string.
serialize explicitly your container classes (by handling each meaningful member data). You could consider using JSON notation (or perhaps YAML, or maybe even XML -which I find too complex so don't recommend) to serialize. It gives you a textual dump format, which you could easily inspect with a standard editor (e.g. emacs or gedit). You'll find a lot of serializing free libraries, e.g. jsoncpp and many others.
learn to compile with g++ -Wall -g and to use the gdb debugger and the valgrind memory leakage detector; also learn to use make and to write your Makefile-s.
take advantage that Linux is free software, so you can look into its source code (and you may want to study stdc++ implementation even if the STL headers are complex).
I'm writing and reading on a binary file. I'm getting small errors when outputting the reads.
The strings are there but with little snippets like: (I"�U) (�U) appended to the end of ~30% of them
I'm using g++ compiler on Ubuntu
Simplified code:
struct Db_connection
{
public:
string name;
}
int Db_connection::write_config()
{
ofstream config_f("config.dat", std::ios_base::binary | std::ios_base::out); //open file
string str = name;
int size = str.length();
config_f.write(reinterpret_cast<char *>(&size), sizeof(int)); // write size of string in int size chunk
config_f.write(str.c_str(), size); //write string
config_f.close();
return 0;
}
Db_connection read_config()
{
ifstream config_f("config.dat", std::ios_base::binary | std::ios_base::in);
Db_connection return_obj;
int size;
string data;
config_f.read(reinterpret_cast<char *>(&size), sizeof(int)); // read string size
char buffer[size];
config_f.read(buffer, size); // read string
data.assign(buffer);
return_obj.name = data;
return return_obj;
}
Is there anything obvious I am messing up? Does this have to do with Endian? I tried to minimize the code to it's absolute essentials
The actual code is more complex. I have a class holding vectors of 2 structs. 1 struct has four string members and the other has a string and bool. These fuctions are actually a member of and return (respectively) that class. The fuctions loop through the vectors writing struct members sequentially.
Two oddities:
To debug, I added outputs of the size and data variables on each iteration in both the read and write functions. size comes out accurate and consistent on both sides. data is accurate on the write side but with the weird special characters on the read side. I'm looking at outputs like:
Read Size: 12
Data: random addy2�U //the 12 human readable chars are there but with 2 extra symbols
The final chunk of data (a bool) comes out fine every time, so I don't think there is a file pointer issue. If its relevant: every bool and int is fine. Its just a portion of the strings.
Hopefully i'm making a bonehead mistake and this minimized code can be critiqued. The actual example would be too long.
Big thanks to WhozCraig,
The following edit did, indeed, work:
Db_connection read_config()
{
ifstream config_f("config.dat", std::ios_base::binary | std::ios_base::in);
Db_connection return_obj;
int size;
string data;
config_f.read(reinterpret_cast<char *>(&size), sizeof(int)); // read string size
vector<char> buff(size);
config_f.read(buff.data(), size);
data = string(buff.begin(), buff.end());
return_obj.name = data;
return return_obj;
}
As paddy pointed out directly and WhozCraig alluded to, this code still needs to implement a standardized, portable data type for recording the integer properly into binary and the write function needs to be rethought as well.
Thank you very much to the both of you. I read like 5-8 top search results for "cpp binary i/o" before writing my code and still ended up with that mess. You guys saved me hours/days of my life.
My problem goes like this: I have a class called 'Register'. It has a string attribute called 'trainName' and its setter:
class Register {
private:
string trainName;
public:
string getTrainName();
};
As a matter of fact, it is longer but I want to make this simpler.
In other class, I copy several Register objects into a binary file, previously setting trainName.
Register auxRegister = Register();
auxRegister.setName("name");
for(int i = 0; i < 10; i++) {
file.write(reinterpret_cast<char*>(&auxRegister),sizeof(Register));
}
Later on, I try to retrieve the register from the binary file:
Register auxRegister = Register();
while(!file.eof()) { //I kwnow this is not right. Which is the right way?
file.read(reinterpret_cast<char*>(&auxRegister), sizeof(Register));
}
It occurs it does not work. Register does, in fact, have more attributes (they are int) and I retrieve them OK, but it's not the case with the string.
Am I doing something wrong? Should I take something into consideration when working with binary files and strings?
Thank you very much.
The std::string class contains a pointer to a buffer where the string is stored (along with other member variables). The string buffer itself is not a part of the class. So writing out the contents of an instance of the class is not going to work, since the string will never be part of what you dump into the file, if you do it that way. You need to get a pointer to the string and write that.
Register auxRegister = Register();
auxRegister.setName("name");
auto length = auxRegister.size();
for(int i = 0; i < 10; i++) {
file.write( auxRegister.c_str(), length );
// You'll need to multiply length by sizeof(CharType) if you
// use a wstring instead of string
}
Later on, to read the string, you'll have to keep track of the number of bytes that were written to the file; or maybe fetch that information from the file itself, depending on the file format.
std::unique_ptr<char[]> buffer( new char[length + 1] );
file.read( buffer, length );
buffer[length] = '\0'; // NULL terminate the string
Register auxRegister = Register();
auxRegister.setName( buffer );
You cannot write string this way, as it almost certainly contains pointers to some structs and other binary stuff that cannot be serialized at all.
You need to write your own serializing function, and write the string length + bytes (for example) or use complete library, for example, protobuf, which can solve serializing problem for you.
edit: see praetorian's answer. much better than mine (even with lower score at time of this edit).
I have the following problem. I have to implement a class that has an attribute that is a char pointer meant to point to the object's "code", as follows:
class foo{
private:
char* cod;
...
public:
foo();
void getVal();
...
}
So on, so forth. getVal() is a method that takes the code from the standard istream and fills in all the information, including the code. The thing is, the "code" that identifies the object can't be longer than a certain number of characters. This has to be done without using customized buffers for the method getVal(), so I can't do the following:
//suppose the maximum number of characters is 50
void foo::getVal()
{
char buffer[100];
cin >> buffer;
if (strlen(buffer) > 50) //I'm not sure this would work considering how the stream
of characters would be copied to buffer and how strlen
works, but suppose this tells me how long the stream of
characters was.
{
throw "Exception";
}
...
}
This is forbidden. I also can't use a customized istream, nor the boost library.
I thought I could find the place where istream keeps its information rather easily, but I can't find it. All I've found were mentions to other types of stream.
Can somebody tell me if this can be done or where the stream keeps its buffered information?
Thanks
yes using strlen would work definitely ..you can write a sample program
int main()
{
char buffer[10];
std::cout << "enter buffer:" ;
std::cin >>buffer;
if(strlen(buffer)>6)
std::cout << "size > 6";
getch();
}
for inputs greater than size 6 characters it will display size >6
uhm .... >> reads up to the first blank, while strlen counts up to the first null. They can be mixed if you know for sure no blanks are in the middle of string you're going to read and that there are no more than 100 consecutive characted. If not, you will overrun the buffer before throwing.
Also, accessing the buffer does not grant all the string to be already there (the string can go past the buffer space, requiring to partially read and refill the buffer...)
If blanks are separator, why not just read into an std::string, and react to its final state? All the dynamics above are already handled inside >> for std::string.
[EDIT after the comments below]
The only way to store a sequence of unknown size, is to dynamically allocate the space and make it grow as it is required to grow. This is, no more - no less, what sting and vector do.
Whether you use them or write your own code to allocate and reallocate where more space is required, doesn't change the substance.
I'm start thinking the only reason of those requirements is to see your capability in writing your own string class. So ... just write it:
declare a class holding a pointer a size and a capacity, allocate some space, track how much you store, and when no store is available, allocate another wider store, copy the old, destroy it, and adjust the data member accordingly.
Accessing directly the file buffer is not the way, since you don't control how the file buffer is filled in.
An istream uses a streambuf.
I find that www.cppreference.com is a pretty good place for quick C++ references. You can go there to see how to use a streambuf or its derivative filebuf.
I've looked at binary reading and writing objects in c++ but are having some problems. It "works" but in addition I get a huge output of errors/"info".
What I've done is
Person p2;
std::fstream file;
file.open( filename.c_str(), std::ios::in | std::ios::out | std::ios::binary );
file.seekg(0, std::ios::beg );
file.read ( (char*)&p2, sizeof(p2));
file.close();
std::cout << "Name: " << p2.name;
Person is a simple struct containing string name and int age. When I run the program it outputs "Name: Bob" since I have already made a program to write to a file (so the object is already in filename).
IN ADDITION to outputting the name it also outputs:
* glibc detected * program: double free og corruption (fastttop): ***
Backtrace:
...
Memory map:
...
Abort
Is the name string in the Person struct a character array or a STL string? You can't fill in an STL String by binary reading data over top of it, since the data format is not serializable (contains pointers)
It would be interesting to see how you write the information to file as well, as well as how the Person struct is built.
If you don't have any problem that the file is plain text, my suggestion would be to write to file using string::c_str() (which returns a const char*) as well as using itoa() or itoa_s() to get the integer as a char*.
You can also have one or several constructors in Person:
Person(const std::string& name, int age);
Person(const char* name, int age);
then, when you extract the data from the file you just call the constructor with that data.
Either p2.name is a char* and you are writing and reading the pointer value, not what is pointed by it. Or p2.name is a more complex type such as std::string which is using internaly pointers with the same problem.
Serializing classes often need more work than just dumping the memory representation.
You said you wrote the Person object to a file. Did you tried to use a dump tool to see if what you have inside the file is what you are expecting?
Also did you tried to instead of using string, use a ordinary char (as #bdk pointed out) ?
When you use binary IO, the size must be fixed. If you use STL string here, it would have a problem as the size of a STL string is arbitrary.