This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How to serialize in c++ ?
I have a class
Class Person
{
int age;
char *name;
char* Serialize()
{
//Need to convert age and name to char* eg:21Jeeva
}
void DeSerialize(char *data)
{
//Need to populate age and name from the data
}
};
In C# we can use MemoryStream,BinrayWriter/BinaryReader to achieve this. In c++ somewhere i found we can use iostream to achieve it. But couldnt able to get a proper example of it.
The purpose of the code is after calling serialize i am going to send the data over socket and in receiving end ill call DeSerialize to populate the members back.
You could take a look at Boost.Serialization. If you only need a simple text-serialization based on iostreams, you probably want to overload the stream extraction operators. For serialization this could look like this:
std::ostream & operator<<(std::ostream & stream, const Person & person) {
stream << person.age << " " << person.name;
return stream;
}
You have to make this function a friend of Person for this to work.
For deserialization, you would use this:
std::istream & operator>>(std::istream & stream, Person & person) {
stream >> person.age >> person.name;
return stream;
}
Using these, you can do the following:
Person fred;
fred.name = "Fred";
fred.age = 24;
std::stringstream buffer;
buffer << fred << std::endl;
Person fred_copy;
buffer >> fred;
You can find a small working example here.
Overloading these operators has the advantage that you can for instance use std::copy with an std::ostream_iterator to serialize an entire collection of Persons in one statement.
You can use following wrapper to convert any datatype to character stream:
template<class TYPE>
string ToChar (const TYPE& value)
{
stringstream ss;
ss << value;
return ss.str();
}
You can use the string object the way you want; like convert into char stream using c_str() or copy into array and so on.
Related
I want to overload the operator >>, so I can read some data from a file to save it in my class. My problem is that I don't know how to read one single word. Does a function like get() or getline() exist for this purpose?
For example, I have this class:
class Person{
private:
char * name;
int id;
public:
....
I have this file with some info:
James 23994
Anne 23030
Mary 300392
And what I want is to read the name and the id of these people to save them in my class.
Things are easier if you use std::string instead of a bare pointer that requires you to allocate memory, keep track of the size (or rely on null-termination), etc...
struct Person {
std::string name;
int id;
};
Now you can use the already existing operator<< for std::string and int :
std::istream& operator>>(std::istream& in, Person& p) {
in >> p.name >> p.id;
return in;
}
Note that operator>> does read input until it finds a ' ' by default, hence to "read a word" you dont have to do anything extra.
If you insist on having private fields, you should declare the operator as friend of the class.
So i written a program where i can input 4 values a first name, last name, height and a signature. I store all values in a Vector but now i would like to learn how i can take the values from my vector and store them in a file and later on read from the file and store back into the vector.
vector<Data> dataVector;
struct Data info;
info.fname = "Testname";
info.lname = "Johnson";
info.signature = "test123";
info.height = 1.80;
dataVector.push_back(info);
Code looks like this i havent found anyway to store objects of a struct into a file so i'm asking the community for some help.
You should provide your struct with a method to write it to a stream:
struct Data
{
// various things
void write_to(ostream& output)
{
output << fname << "\n";
output << lname << "\n";
// and others
}
void read_from(istream& input)
{
input >> info.fname;
input >> info.lname;
// and others
}
};
Or provide two freestanding functions to do the job, like this:
ostream& write(ostream& output, const Data& data)
{
//like above
}
// and also read
Or, better, overload the << and >> operator:
ostream& operator<<(const Data& data)
{
//like above
}
// you also have to overload >>
Or, even better, use an existing library, like Boost, that provides such functionality.
The last option has many pros: you don't have to think how to separate the fields of the struct in the file, how to save more instances in the same file, you have to do less work when refactoring or modifying the struct.
Don't reinvent the wheel: use the Boost serialization libraries.
"Write a class named Person that represents the name and address of a person. Use a string to hold each of these elements. Add operations to read and print Person objects to the code you wrote."
Note I haven't reached the section on access control yet.
#include <iostream>
#include <string>
using std::string;
struct Person
{
string name_var, address_var;
};
std::ostream &print(std::ostream&, const Person &);
std::istream &read(std::istream&, Person &);
std::istream &read(std::istream &is, Person &item)
{
is >> item.name_var >> item.address_var;
return is;
}
std::ostream &print(std::ostream &os, Person &item)
{
os << item.name_var << " " << item.address_var;
return os;
}
With this I can only read single worded names and addresses if I use std::cin as the first argument to read, which isn't very useful. Can you somehow use getline?
You can use:
std::getline(is, item.name_var);
You can also specify a delimiter char as the third argument
Look at one thing: you have used a space to separate name and address. Now, in order to know when the name is over, you need to use some other delimiter for name. Another delimiter means you need to enclose the name in e.g. double quote (") and then, while reading the name, get input until you get the second delimiter.
I'd like to read a file into a struct or class, but after some reading i've gathered that its not a good idea to do something like:
int MyClass::loadFile( const char *filePath ) {
ifstream file ( filePath, ios::in | ios::binary );
file.read ((char*)this, 18);
file.close();
return 0;
}
I'm guessing if i want to write a file from a struct/class this isn't kosher either:
void MyClass::writeFile( string fileName ) {
ofstream file( fileName, ofstream::binary );
file.write((char*)this, 18);
file.close();
}
It sounds like the reason i don't want to do this is because even if the data members of my struct add up to 18 bytes, some of them may be padded with extra bytes in memory. Is there a more correct/elegant way to get a file into a class/struct like this?
The preferred general technique is called serialization.
It is less brittle than a binary representation. But it has the overhead of needing to be interpreted. The standard types work well with serialization and you are encouraged to make your class serialize so that a class containing your class can easily be serialized.
class MyClass {
int x;
float y;
double z;
friend std::ostream& operator<<(std::ostream& s, MyClass const& data);
friend std::istream& operator>>(std::istream& s, MyClass& data);
};
std::ostream& operator<<(std::ostream& s, MyClass const& data)
{
// Something like this
// Be careful with strings (the input>> and output << are not symmetric unlike other types)
return str << data.x << " " << data.y << " " << data.z << " ";
}
// The read should be able to read the version printed using <<
std::istream& operator>>(std::istream& s, MyClass& data)
{
// Something like this
// Be careful with strings.
return str >> data.x >> data.y >> data.z;
}
Usage:
int main()
{
MyClass plop;
std::cout << plop; // write to a file
std::cin >> plop; // read from a file.
std::vector<MyClass> data;
// Read a file with multiple objects into a vector.
std::ifstream loadFrom("plop");
std::copy(std::istream_iterator<MyClass>(loadFrom), std::istream_iterator<MyClass>(),
std::back_inserter(data)
);
// Write a vector of objects to a file.
std::ofstream saveTo("Plip");
std::copy(data.begin(), data.end(), std::ostream_iterator<MyClass>(saveTo));
// Note: The stream iterators (std::istream_iterator) and (std::ostream_iterator)
// are templatized on your type. They use the stream operators (operator>>)
// and (operator<<) to read from the stream.
}
The answer is : there is no silver bullet to this problem.
One way you can eliminate the padding to ensure that the data members in your class is to use(in MSVC which you are using)
#pragma pack( push, 1 )
class YourClass {
// your data members here
int Data1;
char Data2;
// etc...
};
#pragma pack( pop )
The main usefulness of this approach is if your class matches a predefined format such as a bitmap header. If it is a general purpose class to represent a cat, dog, whatever then dont use this approach. Other thing if doing this is to make sure you know the length in bytes of the data types for your compiler, if your code is EVER going to be multi platform then you should use explicit sizes for the members such as __int32 etc.
If this is a general class, then in your save member, each value should be written explicitly. A tip to do this is to create or get from sourceforge or somewhere good code to help do this. Ideally, some code that allows the member to be named, I use something similar to :
SET_WRITE_DOUBLE( L"NameOfThing", DoubleMemberOfClass );
SET_WRITE_INT( L"NameOfThing2", IntMemberOfClass );
// and so on...
I created the code behind these macros, which I am not sharing for now but a clever person can create their own code to save named to stream in an unordered-set. This I have found is the perfect approach because if you add or subtract data members to your class, the save/load is not dependent on the binary representation and order of your save, as your class will doubtless evolve through time if you save sequentially this is a problem you will face.
I hope this helps.
I want to serialize an object of type Person. I want to use it later on for data saving or even game saving. I know how to do it for primitives like int, char, bool, and even c-strings like char[].
The problem is, I want the string to be as big as it needs to rather than declaring a char array of size 256 and hoping no one enters something too big. I read that serializing a class with std::string as a member doesn't work because it has an internal pointer, but is there a way to serialize my class which has a char* as a member?
I realize Boost has a serialization library, but I'd like to do this without the need of external libraries, it seems like a good activity to try.
Here's my Person class:
class Person
{
private:
char* _fname;
char* _lname;
public:
Person();
Person(const char* fname, const char* lname);
Person(const string& fname, const string& lname);
string fname() const;
void fname(const char* fname);
void fname(const string& fname);
string lname() const;
void lname(const char* lname);
void lname(const string& lname);
};
First: Use std::string in your class it will make your life so much easier in the long run.
But this advice works for both std::string and char* (with minor tweaks that should be obvious).
Basically you want to serialize data of unknown size (at compile time). This means when you de-serialize the data you must either have a technique that tells you how long the data is (prefix the object with a size) or a way to find the end of the data (a termination marker).
A termination marker is easier for serialization. But harder for de-serialization (as you must seek forward to find the end). Also you must escape any occurrences of the termination marker within your object and the de-serialization must know about the escaping and remove it.
Thus because of this complications I prefer not to use a termination marker. As a result I prefix the object with a size. The cost of this is that I must encode the size of the object in a way that will not break.
So if we prefix an object with its size you can do this:
// Place a ':' between the string and the size.
// There must be a marker as >> will continue reading if
// fname contains a digit as its first character.
// I don;t like using a space as >> skips spaces if you are not carefull
// and it is hard to tell the start of the string if the first characters in fname
// are the space character.
std::cout << strlen(fname) << ":" << fname;
Then you can de-serialize like this:
size_t size;
char mark;
std::cint >> size >> mark;
if (!std::cin || mark != ':')
{ throw BadDataException;
}
result = new char[size+1](); // Note the () to zero fill the array.
std::cin.read(result, size)
Edit 1 (based on comments) Update: to use with string:
size_t size;
char mark;
std::cint >> size >> mark;
if (!std::cin || mark != ':')
{ throw BadDataException;
}
std::string result(' ', size); // Initialize string with enough space.
std::cin.read(&result[0], size) // Just read directly into the string
Edit 2 (based on commented)
Helper function to serialize a string
struct StringSerializer
{
std::string& value;
StringSerializer(std::string const& v):value(const_cast<std::string&>(v)){}
friend std::ostream& operator<<(std::ostream& stream, StringSerializer const& data)
{
stream << data.value.size() << ':' << data.value;
}
friend std::istream& operator>>(std::istream& stream, StringSerializer const& data)
{
std::size_t size;
char mark(' ');
stream >> size >> mark;
if (!stream || mark != ':')
{ stream.setstate(std::ios::badbit);
return stream;
}
data.value.resize(size);
stream.read(&data.value[0], size);
}
};
Serialize a Person
std::ostream& operator<<(std::ostream& stream, Person const& data)
{
return stream << StringSerializer(data.fname) << " "
<< StringSerializer(data.lname) << " "
<< data.age << "\n";
}
std::istream& operator>>(std::istream& stream, Person& data)
{
stream >> StringSerializer(data.fname)
>> StringSerializer(data.lname)
>> data.age;
std::string line;
std::getline(stream, line);
if (!line.empty())
{ stream.setstate(std::ios::badbit);
}
return stream;
}
Usage:
int main()
{
Person p;
std::cin >> p;
std::cout << p;
std::ofstream f("data");
f << p;
}
You can't serialize pointer, you need to serialize data pointer points to.
You'll need to serialize whole web of objects, starting from Person (or Game) and looking into each object, which is reachable from your start object.
When deserializing, you reading data from your storage, allocate memory for that data and use address of this freshly allocated memory as a member of Person/Game object
Pointer fields make it bit harder, but not impossible to serialize. If you don't want to use any of the serialization libraries, here is how you can do it.
You should determine the size of what is pointed to at the time of serialization (e.g. it may be of fixed size or it may be a C-string with null character at the end), then you can save a mark indicating that you're serializing an indirect object together with size and the actual content of the area pointed to.
When you stumble upon that mark during deserialization, you can allocate the right amount of memory, copy the object into it and store the pointer to the area in the deserialized object.
I recommend using a vector to encapsulate strings for
serialization.
#include <vector>
using namespace std;
map vector<unsigned char> cbuff;
inline cbuff vchFromString(const std::string &str) {
unsigned char *strbeg = (unsigned char*) str.c_str();
return cbuff(strbeg, strbeg + str.size());
}
inline std::string stringFromVch(const cbuff &vch) {
std::string res;
std::vector<unsigned char>::const_iterator vi = vch.begin();
while (vi != vch.end()) {
res += (char) (*vi);
vi++;
}
return res;
}
class Example
{
cbuff label;
Example(string labelIn)
{
SetLabel(labelIn);
}
IMPLEMENT_SERIALIZE
(
READWRITE(label);
)
void SetLabel(string labelIn)
{
label = vchFromString(labelIn);
}
string GetLabel()
{
return (stringFromVch(label));
}
};