Serialize C++ object with dynamically allocated attributes - c++

I need to serialize an instance of a class to a binary (or some other non-readable) file. The problem is:
The attributes are pointers, which means I need to serialize the heap memory.
Some of the pointers are function pointers.
I've partially managed to deal with the first one. Still, to me all this code looks unsafe and confusing, and I'm still looking for a better way.
Entity.h
#include <vector>
#include <string>
using namespace std;
class Entity
{
private:
vector<int> field_1 = {1, 23, 356, 4787, 5};
vector<double> field_2 = {1.1, 75.757, 0, 45612};
public:
void serialize(const string& filename);
void deserialize(const string& filename);
void show();
void clear();
};
Entity.cpp
#include <iostream>
#include <fstream>
#include "Entity.h"
void Entity::serialize(const string& filename) {
fstream file(filename, ios::binary | ios::out);
if (!file.is_open()) {
cout << "Failed to open the file" << endl;
return;
}
size_t size = field_1.size();
file.write((const char*)&size, sizeof size); // First I write the size of the array
file.write((const char*)field_1.data(), sizeof *field_1.data() * size); // The array itself
size = field_2.size();
file.write((const char*)&size, sizeof size);
file.write((const char*)field_2.data(), sizeof *field_2.data() * size);
file.close();
}
void Entity::deserialize(const string& filename) {
fstream file(filename, ios::binary | ios::in);
if (!file.is_open()) {
cout << "Failed to open the file" << endl;
return;
}
size_t size;
file.read((char*)&size, sizeof size); // Read the size
field_1.resize(size);
file.read((char*)field_1.data(), sizeof *field_1.data() * size); // Read the data
file.read((char*)&size, sizeof size);
field_2.resize(size);
file.read((char*)field_2.data(), sizeof *field_2.data() * size);
file.close();
}
void Entity::show() {
if (field_1.size()) for (auto i : field_1) cout << i << ' ';
else cout << "field_1 is empty";
cout << endl;
if (field_2.size()) for (auto i : field_2) cout << i << ' ';
else cout << "field_2 is empty";
cout << endl;
}
void Entity::clear() {
field_1.clear();
field_2.clear();
}
Main.cpp
#include "Entity.h"
int main() {
Entity e;
e.show();
e.serialize("data.bin");
e.clear();
e.show();
e.deserialize("data.bin");
e.show();
}
The output
1 23 356 4787 5
1.1 75.757 0 45612
field_1 is empty
field_2 is empty
1 23 356 4787 5
1.1 75.757 0 45612
As the amount of attributes grows, this code grows significantly. Besides, I have to watch the order of reading the data, which may accidentally lead to messing all the data.
As for the second point, I haven't come up with any ideas yet.
What is the best way to serialize an instance of a class with pointer attributes?

As #fredrik points out, there are plenty of libraries to do this for you, or to help you do it. Use them. Or if you won't ..
You serialize what an object represents, not how it is held in memory at any specific time. So if you have a pointer p to an object of type T, you serialize *p -- the thing it points at. When deserializing, you need to know enough about the structure that you're deserializing to, that you allocate memory for a T -- that recreates a pointer -- and then deserialize to the fresh object of type T.
As an example, consider serializing this type:
struct T { int i; char *s; };
(terrible structure; use better names and don't use raw char pointers). To serialize an object o of this type, write out the serialization of the value of o.i, and then .. well, what does o.s represent? In the vast majority of systems, there's no point in writing out "the string s was at memory address 0xffe8deadbeef". So you write out what it represents. Is it a string with only ASCII characters? You could write its length, followed by the string it points to. You could serialize this specific structure with a single call to printf("%d;%d;%s;",...). Deserializing is left as an exercise.
You serialize what an function represents, not how it is held in memory. Function pointers are the same: there is no point in writing down "the function at address -151" because in the vast majority of systems, that address will be different the next time you start the computer, run the program, whatever. Write out something that allows you to reconstruct what the function pointer was. If you are hanging on to std::function objects or lambda's, then you are likely SOL if you have no idea what the function is supposed to be doing or why it's there.

Related

How can I read .txt files, to inherited objects in c++?

I'm doing an assignment, where I have to create a database in c++ for a sport organization, without using STL (I created my own list and String). The database is keeping the data in a doubly linked list, where besides the nodes, there is the data, which are the teams. Of course there is not just one type of teams, there is currently three. These objects, inherited the teams objects. I have made everything work, except reading the text file, and thus creating objects.
I have already tried, to create a marker, which is the first piece of information the program will read, which will decide which of the three classes needs to be created, then reads the other data, to create the new object, then put it in the end of the doubly linked list. Unfortunately it does'nt work, instead it does nothing, and continue the whole programme, like nothing happened.
void hozzaad(ListaElem *s, team *data) { ///adding to the end
ListaElem *iter = s;
while (iter->kov=NULL)
{
iter = iter->kov;
}
ListaElem *uj = new ListaElem(data);
uj->elozo = iter;
iter->kov = uj;
}
void listaz(ListaElem *s) { //print out all that is in the list
if (s == NULL) {
std::cout << "Ures lista" << std::endl;
return;
}
ListaElem *iter = s;
while (iter!=NULL)
{
iter->adat->kiirt(std::cout);
iter = iter->kov;
}
}
void listament(ListaElem *s, const char *a) { //this one creates the file
std::ofstream file;
file.open(a);
ListaElem *iter = s;
if (file.is_open()) {
while (iter != NULL) {
file << iter->adat->Getclub()<< "\n";
file << iter->adat->Getname() << "\n" << iter->adat->Getmember()<< "\n";
if (iter->adat->Getclub() == 1) {
file << iter->adat->Getsupport() << "\n";
}
if (iter->adat->Getclub() == 2) {
file << iter->adat->Getpompom() << "\n";
}
if (iter->adat->Getclub() == 3) {
file << iter->adat->Getname1() << "\n" << iter->adat->Getname2() << "\n";
}
iter = iter->kov;
}
}
else
{
std::cout << "Nem tudom kinyitni a file-t";
}
file.close();
return;
};
void test4() { // the basic test
Handball c("Kezes HC", 21, 50000);
team adat("", 0);
ListaElem *egyik = new ListaElem(&adat);
hozzaad(egyik,&c);
ListaElem *uj = new ListaElem(&adat);
listament(egyik, "test.txt");
std::ifstream file;
file.open("test.txt");
if (!(file.is_open())) {
std::cout << "hiba\n";
return;
}
int micsoda;
while (file >> micsoda);
{
if (micsoda == 1) {
String beolvas("");
int m;
int d;
getline(file, beolvas);
file >> m;
file >> d;
Handball ujh(beolvas, m, d);
hozzaad(uj, &ujh);
beolvas = "";
}
if (micsoda == 2) {
String fbeolvas("");
int fm;
String e1("");
String e2("");
getline(file, fbeolvas);
file >> fm;
getline(file, e1);
getline(file, e2);
football ujh(fbeolvas, fm, e1, e2);
hozzaad(uj, &ujh);
}
if (micsoda == 3) {
String bbeolvas("");
int bm;
int bd;
getline(file, bbeolvas);
file >> bm;
file >> bd;
Basketball ujh(bbeolvas, bm, bd);
hozzaad(uj, &ujh);
}
}
std::cout << "OK" << std::endl;
listaz(uj);
file.close();
std::cout << "OK" << std::endl;
}
test4() expects to go out like:
OK
Kezes HC 21 50000
OK
Shortening the code to the absolutely necessary:
if (micsoda == 1)
{
Handball ujh;
hozzaad(uj, &ujh);
} // at this point in code, your object ujh runs out of scope!!!
At the time the object runs out of scope, it is destroyed (you will notice if you add some output statement in the class' destructor...) and any pointers to it get invalid; especially, those in the list get invalid (one speaks of dangling pointers – or references, for which the same can happen). Now using them results in undefined behaviour – which means anything could happen. If you're unlucky (or lucky, depending on point of view), your program even crashes.
Be aware that, due to lacking important parts of your code, I am assuming that you only store pointers to the objects in your ListaElem class, which is pretty likely, though, as the classes to be stored are polymorphic...
What you need, though, are objects living longer than just while the programme is in the if block. Assuming we target some real-life scenario, just moving the objects out of the if clauses is no option as we might need more than one single object of the same type. So you will create the objects dynamically. However, then you'll have to deal with memory management and the ownership question as well. Easiest to handle the memory management part is using smart pointers. Ownership? Well, it appears reasonable to me to assume the list being the sole owner of the objects, so you could have:
class ListElem
{
std::unique_ptr<Team> m_data;
public:
ListElem(std::unique_ptr<Team> data) // accepting a unique_ptr already here indicates
// clearly that the item will grab ownership
: m_data(std::move(data)) // unique_ptr is only movable, not copiable!
{ }
}
Then you can change your code above to:
if (micsoda == 1)
{
int n, m; // sample data
hozzaad(uj, std::make_unique<Team>(n, m); // arguments are passed directly to
// the constructor of class Team
}
OK, you are not allowed to use STL; then you'd write your smart pointers on your own, as well as make_unique and move template functions (just as you did for list and string already). On cppreference, you can find a sample implementation, e. g. for std::make_unique. Don't just copy/paste the code, but understand it first, and best re-write it from scratch on your own, otherwise you won't be learning anything (same applies for my code above). Or maybe you ask your teacher if she/he makes an exception on STL for smart pointers.
Final advice: Operating on contiguous memory in general is much faster than operating on memory potentially distributed all over your system RAM. So you might consider rather re-implementing std::vector instead of std::list.

How to iterate over a vector containing many stucts (C++)

// In my Class A, I have many nodes and every node data is stored in a struct like this:
Class A
{
private:
struct BriteNodeInfo
{
int nodeId;
double xCoordinate;
double yCoordinate;
int inDegree;
int outDegree;
int asId;
std::string type;
};
};
// Each node instance is stored in a vector like this:
typedef std::vector<BriteNodeInfo> BriteNodeInfoList;
BriteNodeInfoList m_briteNodeInfoList;
//And then, here is the function that I want to implent down below
void SaveNodeData (std::string fname);
};
Problem: How do I implent that SaveNodeData() function to save my nodes data in .txt file like this?:
nodeId0 yCoordinate0 xCoordinate0
nodeId1 yCoordinate1 xCoordinate1
nodeId2 yCoordinate2 xCoordinate2
nodeId3 yCoordinate3 xCoordinate3
etc...
I have tried but my iteration syntax is not good enough. Here is my function, please help:
Here is my failed function:
void SaveNodeData (std::string fname)
{
ofstream os(fname.c_str(), ios::trunc);
vector<BriteNodeInfo> BriteNodeInfoList;
BriteNodeInfoList m_briteNodeInfoList;
for (BriteNodeInfoList::Iterator i = m_briteNodeInfoList.Begin(); i != m_briteNodeInfoList.End(); ++i)
{
os << BriteNodeInfo[i].nodeId "\t" << "\t" << BriteNodeInfo[i].yCoordinate; << "\t"BriteNodeInfo[i].xCoordinate<< "\n";
}
os << "\n";
}
Before starting, this code as written will clearly have some compile errors. But assuming you can manage to fix those issues, there's one huge flaw.
Your function SaveNodeData creates an empty BriteNodeInfoList, and then tries to read from it. The for loop you have written will always simply exit.
What you need to do is create and populate a BriteNodeInfoList somewhere that this function reads. You could pass it in as an argument to the function, have it as a private variable for class A (assuming SaveNodeData is made into a member of class A). Or you could make it a static member variable of class A (not really recommended: static member variables of objects have some serious problems).
void SaveNodeData (std::string fname) // file path
{
ofstream os(fname.c_str(), ios::trunc);
vector<BriteNodeInfo> BriteNodeInfoList = m_briteNodeInfoList;
for (std::vector<BriteNodeInfoList>::Iterator it = BriteNodeInfoList.Begin(); it != BriteNodeInfoList.End(); ++it)
{
os << (*it).nodeId << "\t" << (*it).yCoordinate << "\t" << (*it).xCoordinate << "\n";
}
}

Run std::function getted by binary read

I'm developing a application and my idea is store "apps" in files, like executables. Now i have that:
AppWriter.c
#include <vector>
#include <time.h>
#include <functional>
struct PROGRAM
{
std::vector<int> RandomStuff;
std::vector<std::function<void()>> Functions;
std::function<void()> MAIN;
} CODED;
void RANDOMFUNC()
{
srand(time(NULL));
for(int i = 0; i < 40; i++)
CODED.RandomStuff.push_back(rand() % 254);
}
void LOGARRAY()
{
for(int i = 0; i < CODED.RandomStuff.size(); i++)
std::cout << "["<< i + 1 <<"]: "<< CODED.RandomStuff[i] << std::endl;
}
void PROGRAMMAIN()
{
std::cout << "Hello i call random function!" << std::endl;
CODED.Functions[0]();
CODED.Functions[1]();
}
void main()
{
CODED.MAIN = PROGRAMMAIN;
CODED.Functions.push_back(RANDOMFUNC);
CODED.Functions.push_back(LOGARRAY);
std::cout << "Testing MAIN" << std::endl;
CODED.MAIN();
FILE *file = fopen("TEST_PROGRAM.TRI","wb+");
fwrite(&CODED,sizeof(CODED),1,file);
fclose(file);
std::cout << "Program writted correctly!" << std::endl;
_sleep(10000);
}
AppReader.c
#include <iostream>
#include <vector>
#include <time.h>
#include <functional>
struct PROGRAM
{
std::vector<int> RandomStuff;
std::vector<std::function<void()>> Functions;
std::function<void()> MAIN;
} DUMPED;
void main()
{
FILE *file = fopen("TEST_PROGRAM.TRI","rb+");
fseek(file,0,SEEK_END);
int program_len = ftell(file);
rewind(file);
fread(&DUMPED,sizeof(PROGRAM),1,file);
std::cout
<< "Function array size: " << DUMPED.Functions.size() << std::endl
<< "Random Stuff Array size: " << DUMPED.RandomStuff.size() << std::endl;
DUMPED.MAIN();
}
When i run AppReader the functions dont work(Maybe why std::function it's like void pointers?), but in arrays or if i add variables i can see with debugger the data are storaged correctly (for that i tryed the vector of functions), but whatever doesn't work throw's me error on functional file. ¿Any ideas how i can do that?
This is never going to work. At all. Ever. std::function is a complex type. Binary reads and writes don't work for complex types. They never can. You would have to ask for functions in a pre-defined serializable format, like LLVM IR.
Your problem is that you're storing information about functions that exist in one executable, then trying to run them in a separate executable. Other than that, your code does work, but as DeadMG says, you shouldn't be storing complex types in a file. Here's how I modified your code to prove that your code works if run within a single executable:
#include <iostream>
#include <vector>
#include <time.h>
#include <functional>
struct PROGRAM
{
std::vector<int> RandomStuff;
std::vector<std::function<void()>> Functions;
std::function<void()> MAIN;
} CODED;
void RANDOMFUNC()
{
srand(time(NULL));
for(int i = 0; i < 40; i++)
CODED.RandomStuff.push_back(rand() % 254);
}
void LOGARRAY()
{
for(int i = 0; i < CODED.RandomStuff.size(); i++)
std::cout << "["<< i + 1 <<"]: "<< CODED.RandomStuff[i] << std::endl;
}
void PROGRAMMAIN()
{
std::cout << "Hello i call random function!" << std::endl;
CODED.Functions[0]();
CODED.Functions[1]();
}
int main()
{
CODED.MAIN = PROGRAMMAIN;
CODED.Functions.push_back(RANDOMFUNC);
CODED.Functions.push_back(LOGARRAY);
std::cout << "Testing MAIN" << std::endl;
CODED.MAIN();
FILE *file = fopen("TEST_PROGRAM.TRI","wb+");
fwrite(&CODED,sizeof(CODED),1,file);
fclose(file);
std::cout << "Program writted correctly!" << std::endl;
// _sleep(10000);
std::cout << "---------------------\n";
file = fopen("TEST_PROGRAM.TRI","rb+");
fseek(file,0,SEEK_END);
int program_len = ftell(file);
rewind(file);
fread(&CODED,sizeof(PROGRAM),1,file);
std::cout
<< "Function array size: " << CODED.Functions.size() << std::endl
<< "Random Stuff Array size: " << CODED.RandomStuff.size() << std::endl;
CODED.MAIN();
}
The problem is not that you're storing complex types via binary read/write, per se. (Although that is a problem, it's not the cause of the problem you posted this question about.) Your problem is that your data structures are storing information about the functions that exist in your 'writer' executable. Those same functions don't even exist in your 'reader' executable, but even if they did, they likely wouldn't be at the same address. Your data structures are storing, via std::function, pointers to the addresses where the functions exist in your 'writer' executable. When you try to call these non-existent functions in your 'reader' executable, your code happily tries to call them but you get a segfault (or whatever error your OS gives) because that's not the start of a valid function in your 'reader' executable.
Now with regard to writing complex types (e.g. std::vector) directly to a file in binary format: Doing so "works" in the sample code above only because the binary copies of the std::vectors have pointers that, once read back in, still point to valid data from the original std::vectors which you wrote out. Note that you didn't write the std::vector's actual data, you only wrote their metadata, which probably includes things like the length of the vector, the amount of memory currently allocated for the vector, and a pointer to the vector's data. When you read that back, the metadata is correct except for one thing: Any pointers in it are pointing to addresses that were valid when you wrote the data, but which may not be valid now. In the case of the sample code above, the pointers end up pointing to the same (still valid) data from the original vectors. But there's still a problem here: You now have more than one std::vector that thinks they own that memory. When one of them is deleted, it will delete the memory that the other vector expects to still exist. And when the other vector is deleted, it will cause a double-delete. That opens the door to all kinds of UB. E.g. that memory could have been allocated for another purpose by that time, and now the 2nd delete will delete that other purpose's memory, or else the memory has NOT been allocated for another purpose and the 2nd delete may corrupt the heap. To fix this, you'd have to serialize out the essence of each vector, rather than their binary representation, and when reading it back in, you'd have to reconstruct an equivalent copy, rather than simply reconstitute a copy from the binary image of the original.

how to get char pointer from ostringstream without a copy in c++

I have a ostringstream variable which contains some data.
I want to get set a char * pointer to the data inside the ostringstream.
If I do the following:
std::ostringstream ofs;
.....
const char *stam = (ofs.str()).c_str();
There is a copy of the content of the string in ofs.
I want to get a pointer to that content without a copy.
Is there a way to do so?
This actually answers the question... took a while but I wanted to do it for the same reasons (efficiency vs portability is fine for my situation):
class mybuf : public std::stringbuf {
public:
// expose the terribly named end/begin pointers
char *eback() {
return std::streambuf::eback();
}
char *pptr() {
return std::streambuf::pptr();
}
};
class myos : public std::ostringstream {
mybuf d_buf;
public:
myos() {
// replace buffer
std::basic_ostream<char>::rdbuf(&d_buf);
}
char *ptr();
};
char *myos::ptr() {
// assert contiguous
assert ( tellp() == (d_buf.pptr()-d_buf.eback()) );
return d_buf.eback();
}
int main() {
myos os;
os << "hello";
std::cout << "size: " << os.tellp() << std::endl;
std::string dat(os.ptr(),os.tellp());
std::cout << "data: " << dat << std::endl;
}
This points to, yet again, the deeper, underlying problem with the standard library - a confusion between contracts and "safety". When writing a messaging service, I need a library with efficient contracts... not safety. Other times, when writing a UI, I want strong safety - and cares less about efficiency.
Although you can't get a pointer to the character buffer in the ostringstream, you can get access to its characters without copying them if you switch to using stringstream. A stringstream allows input and output (reading from and writing to the stream), whereas ostringstream allows only output (writing to the stream). Example:
std::stringstream ss;
ss << "This is a test.";
// Read stringstream from index 0. Use different values to look at any character index.
ss.seekg(0);
char ch;
while (ss.get(ch)) { // loop getting single characters
std::cout << ch;
}
ss.clear(); // Clear eof bit in case you want to read more from ss
This site has a pretty good overview of stringstreams and what you can do with them.

c++ overload pointer ostream

I am a learning c++ and have a class project due in 5 days. I've spent 4 hours researching how to do this however I have not come up with an answer yet. Save me stack!
Problem. I have a pointer to a class which holds a dynamic array. I need to take that array and save it to a file to retrieve later. Here are my 2 headers and the implementation. I am not writing the code to "save to file" yet as that will be easy once I get around this issue. My problem is it keeps printing the address of the pointer and not the data within.
vehReg.h
class vehReg {
public:
/* STUFF */
};
}
#endif
vehData.h
#include "vehReg.h"
using namespace std;
class vehData {
public:
//CONSTRUCTORS
vehData();
//DECONSTRUCTOR
~vehData();
//METHODS
friend ostream &operator<<( ostream &output, const vehData &v);
private:
typedef unsigned long longType;
typedef std::size_t sizeType;
sizeType used,capacity;
vehReg *data;
};
}
#endif
vehData.cpp
//CONSTRUCTOR
vehData::vehData(){
capacity = 5;
used = 0;
data = new vehReg[capacity];
}
//DECONSTRUCTOR
vehData::~vehData(){
delete []data;
}
/* TRYING TO ACCOMPLISH THIS WITH AN OSTREAM OVERLOAD */
void vehData::saveDataSloppy(){
ofstream myFile;
myFile.open ("database.db");
for(int i=0;i<used;i++){
myFile << data[i].getOwnerName() << "|";
myFile << data[i].getVehicleLicense() << "|";
myFile << data[i].getVehicleMake() << "|";
myFile << data[i].getVehicleModel() << "|";
myFile << data[i].getVehicleYear() << "\n";
}
myFile.close();
}
void vehData::saveData(){
cout << data;
}
ostream &operator<<(ostream &stream, const vehData &v){
stream << v.data;
}
}
v.data is a pointer, so it prints a pointer. How do you want it to
print whatever the pointer points to. With the exception of character
pointers, the << always prints what you give it (formatted in some
way). If you don't want it to print a pointer, give is something else.
Suppose it did dereference the pointer. What should it print: one
vehReg? 20? A pointer has no information concerning the size. If
you'd used std::vector<vehReg> (a much better choice), it would know
the size, but there's still no overload on std::vector, since the
system still doesn't know how you want it formatted (comma separated?
each on a new line?). And you've not told it how to print a vehReg
either.
You apparently understand the idea of how to overload <<. The first
thing you'll have to do is provide an overload for vehReg as well.
And both overloads must be defined in terms of existing overloads:
there's not one for std::vector, and the one for pointer doesn't do
what you want (and couldn't), so you'll have to loop in your << for
vehData and output each element, with whatever separators you decide
on. (If it's each element on its own line, then you can use std::copy
and an ostream_iterator for the loop, but this may be a bit in advance
of what you've learnt so far.) And forward to the << for vehReg for
each vehReg.
v.data is a pointer so it's a memory address.
*v.data is what the pointer is pointing to (which in this case is an integer).
For example,
#include <iostream>
using namespace std;
void main () {
int *ptr;
int var = 5;
ptr = &var;
cout << ptr << endl;
cout << *ptr << endl;
system("pause");
}
First line will print out something like: 0043F930
Second line will print out: 5
This should print out the elements held in the data array.
void vehData::showStructure() const {
for (int i = 0; i < capacity: i++) {
cout << data[i];
}
cout << endl;
}