How can I use boost serialization to save and get multiple objects (number of objects varies)?
For example I have class Contact, and I input contact and save it to file. Another time I input another contact, and it should also be saved in file.
I think the save function should be like this:
void save_contact(const Contact &s, const char * filename){
std::ofstream ofs(filename, std::ios::app);
boost::archive::text_oarchive oa(ofs);
oa << s;
ofs << "\n";
}
And to retrieve the contacts I should keep track of contacts number, am I right?
To retrieve single contact I use the following code:
void retrieve_contact(Contact &s, const char * filename)
{
std::ifstream ifs(filename);
boost::archive::text_iarchive ia(ifs);
ia >> s;
}
Here is how serialization function inside Contact class looks like:
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & m_CompanyName;
ar & m_FirstName;
ar & m_LastName;
ar & m_PhoneNumbers;
}
(m_CompanyName, m_FirstName and m_LastName are std::string, m_PhoneNumbers is std::vector)
So is there way somehow to retrieve contacts without keeping track of number of contacts? Or can you suggest me another way to save and retrieve contacts, saved at different time? Also how can I edit the saved file to modify Contact?
Yeah, use the predefined serialization for standard containers. So, e.g.
#include <boost/serialization/vector.hpp>
And then
void retrieve_contact(std::vector<Contact>& s, const char * filename)
{
std::ifstream ifs(filename);
boost::archive::text_iarchive ia(ifs);
ia >> s;
}
Related
I created an output text_archive and I restored it using binary archive and obviously, got some issues.
Can I know somehow the kind of archive, so that I could possibly use the appropriate code for binary/xml/text archive.
class Info
{
private:
// Allow serialization to access non-public data members.
friend class boost::serialization::access;
// Serialize the std::vector member of Info
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & filenames;
}
std::vector<std::string> filenames;
};
int main(int argc, char** argv)
{
Info info;
// Save filename data contained in Info object
{
// Create an output archive
std::ofstream ofs( "store.dat" );
**boost::archive::text_oarchive ar(ofs);**
ar & info;
}
// Restore from saved data and print to verify contents
Info restored_info;
{
// Create and input archive
std::ifstream ifs( "store.dat" );
**boost::archive::binary_iarchive ar(ifs);**
// Load the data
ar & restored_info;
}
return 0;
}
You can use first two bytes to specific type, let's say
00 for binary
01 for xml
10 for text
and the reset of the content is for data itself
I used this method to serialize my object :
void save(Obj& obj) {
ofstream os("obj.dat", ofstream::binary);
boost::archive::binary_oarchive ar(os, boost::archive::no_header);
ar << boost::serialization::make_binary_object(&obj, sizeof(obj));
}
What would be my the code for my Obj load(string fileName) ?
It's basically the same as what you had:
Obj load(std::string const& filename) {
std::ifstream is(filename, std::ios::binary);
boost::archive::binary_iarchive ar(is, boost::archive::no_header);
Obj obj;
ar >> boost::serialization::make_binary_object(&obj, sizeof(obj));
return obj;
}
Of course, this is assuming that your type is valid for use with make_binary_object: make sure that Obj is bitwise serializable (POD):
static_assert(std::is_pod<Obj>::value, "Obj is not POD");
Also, reconsider using namespace: Why is "using namespace std;" considered bad practice?
I am trying to work with boost::serialisation for saving and loading some objects.
So far from the boost::tutorial I have managed to do things work for all the different stl stuff (vectors, pairs, lists etc), for derived classes, for boost multi-arrays and other things, but I am stuck trying to work around how to serialize a boost::any vector.
Let me say in advance that I found and checked in the forum some posts for boost::varian serialization and even one for boost::any (which even has an almost identical title) but yet again I didn't manage to solve my problems.
So let me go with a small example:
Say I have this class:
class class_2
{
private:
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & degrees;
ar & minutes;
ar & seconds;
for( std::vector<boost::any>::iterator it = v_any.begin() ; it != v_any.end() ; ++it ) {
//Trying to cast all the possible types that may enter the
//boost any vector, for the sake of this example let's just
//say that we will only pass a second class called name class_1
//to the boost::any vector and we only have to cast this class
if (it->type() == typeid(class_1)){
class_1 lan = boost::any_cast< class_1 > (*it );
ar & (lan);
}
}// for boost::any*/
} //serialization
public:
int degrees;
int minutes;
float seconds;
class_2(){};
class_2(int d, int m, float s) :
degrees(d), minutes(m), seconds(s)
{}
std::vector<boost::any> v_any;
};
And to be more precise the class_1 that we expect for this small example to exist inside the boost::any vector is the following class:
class class_1
{
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & a;
}
public:
class_1(){};
int a;
};
So when I compile the above code with a main function where I save and load an object of class_2 that containts inside the boost::any vector an object of class_1 everything compiles and even runs:
int main() {
class_1 alpha;
class_2 beta;
alpha.a=5;
beta.v_any.push_back(alpha);
std::ofstream ofs("file");
// save data to archive
{
boost::archive::text_oarchive oa(ofs);
// write class instance to archive
oa << beta;
// archive and stream closed when destructors are called
}
// ... some time later restore the class instance to its orginal state
class_2 newclass;
{
// create and open an archive for input
std::ifstream ifs("file");
boost::archive::text_iarchive ia(ifs);
// read class state from archive
ia >> newclass;
// archive and stream closed when destructors are called
}
return 0;
}
Yet again the loaded newclass object has an empty boost::any vector with nothing saved inside.
So my question is what am I doing wrong in the above code and what should I change in order to serialize the boost::any vector correctly..?
Any help/suggestion would be very appreciated.
I have two classes that will represent two very simple databases, and each has a "Save" function which will write what's in the class to a file. Since the code within the "Save" function is very similar, I was wondering if I could factor it out.
One of my colleagues said this might be possible with inheritance and/or metadata, so I tried looking into it myself with Google. However, I couldn't find anything that was helpful and am still unsure if what I want to do is even possible.
If it's possible to factor out, then I think I'd need to have another class or function know about each class's types and iterate through them somehow (metadata?). It would check the type of every data, and depending on what the type is, it would make sure that it's correctly output to the text file.
(I know data like name, age, etc. should be private, but to keep this simple I just had everything be public)
class A
{
public:
A() : name(""), age(0) {};
void Save(void)
{
std::string filename = "A.txt";
std::string data;
data += name + "\n";
data += std::to_string(age) + "\n";
std::ofstream outfile(filename);
outfile.write(data.c_str(), data.size());
outfile.close();
}
std::string name;
int age;
};
class B
{
public:
B() : ID(0), points(0) {};
void Save(void)
{
std::string filename = "B.txt";
std::string data;
data += std::to_string(ID) + "\n";
data += std::to_string(points) + "\n";
std::ofstream outfile(filename);
outfile.write(data.c_str(), data.size());
outfile.close();
}
int ID;
int points;
};
int main(void)
{
A a;
B b;
a.name = "Bob"; a.age = 20;
b.ID = 4; b.points = 95;
a.Save();
b.Save();
return 0;
}
A possible solution could be to use metaprogramming (not sure what you mean by metadata), i.e. templates to reuse the common parts
template<typename T1, typename T2>
void TSave(const std::string fname, const T1& p1, const T2& p2) {
std::string filename = fname;
std::stringstream data;
data << p1 << "\n";
data << p2 << "\n";
std::ofstream outfile(filename);
outfile.write(data.str().c_str(), data.str().size());
outfile.close();
}
class A {
...
void Save(void) {
TSave("A.txt", name, age);
}
std::string name;
int age;
};
class B {
...
void Save(void) {
TSave("B.txt", ID, points);
}
int ID;
int points;
};
Live Example
What you are looking for is serialization: saving objects to a file (and one day or another, restore the objects).
Of course, you could write your own serialization framework, and Marco's answer is an interesting start in that direction. But alternatively, you could consider existing libraries, such as boost::serialization :
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
class A {
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & name;
ar & age;
}
...
};
class B {
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & ID;
ar & points;
}
...
};
main() {
A a;
B b;
...
{
std::ofstream ofs("myfile");
boost::archive::text_oarchive arch(ofs);
arch << a << b;
}
}
As you see, it's still needed to say what's to be written to the file. However, the code is simplified : you don't have to worry about file management and transformation of data. And it works also with standard containers.
You won't find a C++ trick that automatically determines for a class what's to be saved. Two reasons for that:
C++ allows metaprogramming, but it is not reflexive: there are no standard process to find out at execution time which members compose a class.
In an object, some data can be transient, i.e. it means only something at the time of the execution and depends on the context. For example pointers: you could save the value of a pointer to a file, but it will mean nothing when you reload it later (the pointer is only valid until you free the object). The proper way would be to save the object that is pointed to (but where, when, how?).
I want to work with file streams generically. That is, i want to 'program to an interface and not the implementation'. Something like this:
ios * genericFileIO = new ifstream("src.txt");
getline(genericFileIO, someStringObject);//from the string library; dont want to use C strings
genericFileIO = new ofstream("dest.txt");
genericFileIO -> operator<<(someStringObject);
Is it possible? I am not great with inheritance. Given the io class hierarchy, how do i implement what i want?
Do you mean:
void
pass_a_line(std::istream& in, std::ostream& out)
{
// error handling left as an exercise
std::string line;
std::getline(in, line);
out << line;
}
This can work with anything that is an std::istream and std::ostream, like so:
// from a file to cout
// no need to new
std::ifstream in("src.txt");
pass_a_line(in, std::cout);
// from a stringstream to a file
std::istringstream stream("Hi");
std::ofstream out("dest.txt");
pass_a_line(stream, out);
This does what your example do, and is programmed against the std::istream and std::ostream interfaces. But that's not generic programming; that's object oriented programming.
Boost.Iostreams can adapt classes to std::[i|o|io]streams, and does this using generic programming.
You can use different specialisations of the ostream or istream concepts over the ostream or istream interface.
void Write(std::ostream& os, const std::string& s)
{
os << "Write: " << s;
}
std::string Read(std::istream& is)
{
std::string s;
is >> s;
return s;
}
int main()
{
Write(std::cout, "Hello World");
std::ofstream ofs("file.txt");
if (ofs.good())
Write(ofs, "Hello World");
std::stringstream ss;
Write(ss, "StringStream");
Write(std::cout, ss.str());
std::string s = Read(std::cin);
Write(std::cout, s);
return 0;
}