I'm new to C so I'm not fully used to the syntax yet. I do know more than the basics of C# syntax tho, which I have been using for the better part of a year now, and I have 2 years of PHP and python to go with that.
I'm tryng to write a struct with a vector inside of it to a file, and read it back at a different moment;
struct inventoryItem
{
std::string itemName;
bool unlocked{false};
};
struct saveData
{
std::string userName{"John"};
int hunger{100};
int health{100};
int money{5};
std::vector<inventoryItem> inventory;
};
I'm saving it to the file like so:
void saveGame::createSave()
{
saveData saveObj;
inventoryItem newItem;
newItem.itemName = "shoes";
newItem.unlocked = true;
saveObj.inventory.push_back(newItem);
newItem.itemName = "knife";
saveObj.inventory.push_back(newItem);
// std::thread saveThis(save, saveObj);
// saveThis.join();
save(saveObj);
return;
}
void saveGame::save(saveData toSave)
{
std::fstream fileW(saveGame::fileName, std::ios_base::in);
if (!fileW.good() || !fileW.is_open())
{
system("exit");
} else
{
fileW.write((char *) &toSave, sizeof(saveData));
fileW.close();
}
return;
}
and I retrieve it like so:
saveGame::saveData saveGame::getSave()
{
std::fstream fileR(saveGame::fileName, std::ios_base::out);
saveData toRetrieve;
if (!fileR.good() || !fileR.is_open())
system("exit");
else
{
fileR.read((char *) &toRetrieve, sizeof(saveData));
}
return toRetrieve;
}
saveGame::saveData data = saveObj.getSave();
std::cout<<data.userName<<std::endl;
std::cout<<data.health<<std::endl;
std::cout<<data.inventory[0].itemName; // I've also tried looping through the vector, but this also did not work. Even when I don't try to access it, it will crash after it has processed the other pieces of data
Now, everything works correctly. And I can access all the data, except for the vector struct that's inside of it. As soon as it gets to that piece of data, the program crashes without a single error code. I do, however get a windows message saying "a.exe has stopped working" but it does not produce a Debug message. If possible, could you use simple terms in the explanation? I haven't gotten to advanced C yet. Thanks in advance
I've tried looping through the vector, not accessing it at all, and simply printing the first piece of data from it.
I am expecting this:
*CONSOLE OUTPUT*
John //username
100 // health
shoes // inventory.itemName (string)
true // inventory.unlocked (bool)
knife // inventory.itemName (string)
false // inventory.unlocked (bool)
But I am instead getting
*CONSOLE OUTPUT*
John //username
100 // health
and... BAM "a.exe has stopped working" without a debug message
CodePudding user response:
A std::vector<> contains a length and a pointer to heap memory. You cannot save a vector like this:
vector<int> v = {1, 2, 3};
// the following line will save the length and the address of the array
// containing the data. NOT the data itself.
fileW.write((char *) &v, sizeof(v));
//...
std::vector<int> u;
// the following line will fill vector u with a length and a pointer to nowhere!
fileX.read((char *) &u, sizeof(u));
Note that the length and pointer to the data are PRIVATE members of std::vector<>. Manipulating them directly can only lead to undefined behavior.
To save a dynamic length array, you must first define a syntax for saving an array to your file, then a syntax for saving the individual items in your file.
In your particular case, you should also define a syntax for saving inventoryItem
objects, because it contains a variable length string.
You can choose any syntax you want to save your data. It could be in binary format, with for example the length followed by the contents of the array, or use a library to save in a popular format, like xml or json, or .ini. No matter which solution you choose, you will need to write functions to save and read the types you defined:
struct inventoryItem
{
std::string itemName;
bool unlocked{false};
friend std::ostream& operator << (std::ostream& os, const inventoryItem& item)
{
return os << itemName << ' ' << (unlocked ? "true" : "false");
}
friend std::istream& operator >> (std::istream& is, inventoryItem& item)
{
std::string unl;
is >> itemName >> unl;
item.unlocked = (unl == "true");
return is;
}
};
struct saveData
{
std::string userName{"John"};
int hunger{100};
int health{100};
int money{5};
std::vector<inventoryItem> inventory;
friend std::ostream& operator << (std::ostream& os, const saveData& data)
{
os << data.userName << " "
<< data.hunger << " "
<< data.health << " "
<< data.money << " ";
os << data.inventory.size() << "\n";
for (size_t i = 0; i < data.inventory.size(); i)
os << data.inventory[i] << "\n";
}
friend std::istream& operator >> (std::istream& is, saveData& data)
{
is >> data.userName >> data.hunger >> data.health >> data.money;
size_t item_count = 0;
is >> item_count;
for (size_t i = 0; i < item_count; i)
{
inventoryItem item;
is >> item;
data.inventory.push_back(item);
}
return is;
}
};
NOTE: This code is not tested and may not even compile, it' only purpose is to show a simple way to stream dynamic data to/from a file. This very simple approach is usually too naive to be very useful. That's why you really should look into using a library, like boost::property_tree, to save into a more robust format.