So I'm building a simulated file-system in C++ to study the language better and maybe some system level programming. Im using Boost::Serialization to save the state of the file system when the user exits, but I'm having trouble in saving/loading my classes. Here is my base class:
enum filetype { FSFILE, FSDIRECTORY, ROOT };
class FileObject {
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int) {
ar & BOOST_SERIALIZATION_NVP(name);
ar & BOOST_SERIALIZATION_NVP(date_of_creation);
ar & BOOST_SERIALIZATION_NVP(type);
}
protected:
std::string name;
std::string date_of_creation;
filetype type;
Here is my first derived class that will be basically a .txt
file in the system:
class File : public FileObject {
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int) {
ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(FileObject);
ar & BOOST_SERIALIZATION_NVP(content);
ar & BOOST_SERIALIZATION_NVP(size);
}
protected:
std::string content;
int size;
And finally here is the Directory
class that will act as the directory that holds files and/or other directories:
class Directory : public FileObject {
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int) {
ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(FileObject);
ar & BOOST_SERIALIZATION_NVP(num_of_contents);
ar & BOOST_SERIALIZATION_NVP(size_of_contents);
for (auto it = this->contents.begin(); it != this->contents.end(); it++) {
ar & BOOST_SERIALIZATION_NVP(it->second);
}
}
protected:
int num_of_contents;
int size_of_contents;
public:
std::unordered_map<std::string, FileObject *> contents;
In my main.cc file I have 2 functions, one for saving and one for loading
void save_state(const Directory &s, const char * filename){
// make an archive
std::ofstream ofs(filename);
assert(ofs.good());
boost::archive::xml_oarchive oa(ofs);
oa << BOOST_SERIALIZATION_NVP(s);
}
void
restore_state(Directory &s, const char * filename)
{
// open the archive
std::ifstream ifs(filename);
assert(ifs.good());
boost::archive::xml_iarchive ia(ifs);
ia >> BOOST_SERIALIZATION_NVP(s);
}
And finally here is the rest main.cc
file that creates some files and dirs and saves/loads for testing purposes :
int main() {
char c;
scanf("%c", &c);
std::string filename(boost::archive::tmpdir());
filename += "/demo_save.xml";
if (c == 's') {
File file("test_file1", filetype::FSFILE);
File file2("test_file2", filetype::FSFILE);
File file3("test_file3", filetype::FSFILE);
File file4("test_file4", filetype::FSFILE);
Directory dir("test_dir1", filetype::FSDIRECTORY);
Directory dir2("test_dir2", filetype::FSDIRECTORY);
dir.insertContent(file.getName(), &file);
dir.insertContent(file2.getName(), &file2);
dir2.insertContent(file3.getName(), &file3);
dir2.insertContent(file4.getName(), &file4);
dir.insertContent(dir2.getName(), &dir2);
save_state(dir, filename.c_str());
}
Directory newd;
if (c == 'l') {
restore_state(newd, filename.c_str());
for (auto it = newd.contents.begin(); it != newd.contents.end(); it++) {
if (it->second->getType() == filetype::FSFILE) {
std::cout << it->first << std::endl;
}
else if (it->second->getType() == filetype::FSDIRECTORY) {
for (auto jt = ((Directory *)it->second)->contents.begin(); jt != ((Directory *)it->second)->contents.end(); jt++) {
std::cout << jt->second->getName() << std::endl;
}
}
}
}
return 0;
}
The programm compiles fine but i get seg fault in the second loop. And from reading the .xml
file, the files inside the dir2
dont get properly serialized.
Are my classes and functions correct? Is this the correct way to serialize an unordered_map
that holds pointers to other classes?
As others have pointed out you have ownership issues. You can serialize pointers but deserializing will result in memory leaks.
Instead, make the pointers owned. I will use unique_ptr
to manage that instead of writing lots of code to manage the lifetimes correctly.
std::unordered_map<std::string, std::unique_ptr<FileObject> > contents;
Then, you must make sure the hierarchy is virtual, by at least adding
virtual ~FileObject() = default;
I've elected to make the FileObject
streamable by also supplying a print
virtual method.
Lastly, register the types, or mark them abstract as appropriate:
BOOST_SERIALIZATION_ASSUME_ABSTRACT(FileObject)
BOOST_CLASS_EXPORT(File)
BOOST_CLASS_EXPORT(Directory)
#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/serialization/access.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/unique_ptr.hpp>
#include <boost/serialization/unordered_map.hpp>
#include <filesystem>
#include <iostream>
#include <set>
namespace fs = std::filesystem;
enum filetype { FSFILE, FSDIRECTORY, ROOT };
class FileObject {
public:
std::string getName() const { return name; }
virtual ~FileObject() = default;
virtual void print(std::ostream& os, std::string const& prefix = "") const {
os << "[" << prefix << "/]" << getName() << std::endl;
}
private:
friend class boost::serialization::access;
template <class Ar> void serialize(Ar& ar, unsigned) {
ar& BOOST_NVP(name) & BOOST_NVP(date_of_creation) & BOOST_NVP(type);
}
protected:
FileObject() = default; // only for deserialization
FileObject(std::string name, filetype type) : name(std::move(name)), type(type) {
// TODO date_of_creation
}
std::string name;
std::string date_of_creation;
filetype type = ROOT;
friend std::ostream& operator<<(std::ostream& os, FileObject const& fo) {
fo.print(os);
return os;
}
};
class File : public FileObject {
public:
File(std::string name, size_t size) : FileObject(std::move(name), FSFILE), size(size) {}
private:
File() = default; // only for deserialization
friend class boost::serialization::access;
template <class Ar> void serialize(Ar& ar, unsigned) {
ar& BOOST_SERIALIZATION_BASE_OBJECT_NVP(FileObject) & BOOST_NVP(content) & BOOST_NVP(size);
}
protected:
std::string content;
size_t size = 0;
};
class Directory : public FileObject {
public:
Directory() : FileObject("/", ROOT) {}
Directory(std::string name, filetype type = FSDIRECTORY) : FileObject(name, type) {
assert(FSDIRECTORY == type);
}
bool insertContent(std::unique_ptr<FileObject> object) {
std::string name = object->getName();
auto [it, ok] = contents.emplace(std::move(name), std::move(object));
if (ok)
num_of_contents += 1; // TODO size_of_contents?
assert(contents.size() == num_of_contents);
return ok;
}
private:
std::unordered_map<std::string, std::unique_ptr<FileObject> > contents;
friend class boost::serialization::access;
template <class Ar> void serialize(Ar& ar, unsigned) {
ar& BOOST_SERIALIZATION_BASE_OBJECT_NVP(FileObject) & BOOST_NVP(num_of_contents) &
BOOST_NVP(size_of_contents) & BOOST_NVP(contents);
}
protected:
size_t num_of_contents = 0;
size_t size_of_contents = 0;
virtual void print(std::ostream& os, std::string const& prefix) const override {
FileObject::print(os, prefix);
for (auto const& [n, obj] : contents)
if (obj)
obj->print(os, prefix + "/" + getName());
}
};
BOOST_SERIALIZATION_ASSUME_ABSTRACT(FileObject)
BOOST_CLASS_EXPORT(File)
BOOST_CLASS_EXPORT(Directory)
#include <fstream>
static inline void save_state(Directory const& s, fs::path const& filename) {
std::ofstream ofs(filename);
boost::archive::xml_oarchive oa(ofs);
oa << BOOST_NVP(s);
}
static inline void restore_state(Directory& s, fs::path const& filename) {
std::ifstream ifs(filename);
boost::archive::xml_iarchive ia(ifs);
ia >> BOOST_NVP(s);
}
int main(int argc, char** argv) {
std::set<std::string_view> const args(argv + 1, argv + argc);
auto filename = fs::temp_directory_path() / "demo_save.xml";
if (args.contains("save")) {
Directory dir("test_dir1", filetype::FSDIRECTORY);
for (auto name : {"test_file1", "test_file2", "test_file3", "test_file4"})
dir.insertContent(std::make_unique<File>(name, filetype::FSFILE));
dir.insertContent(std::make_unique<Directory>("test_dir2"));
save_state(dir, filename);
}
if (args.contains("load")) {
Directory newd;
restore_state(newd, filename);
std::cout << newd;
}
}
Prints
[/]test_dir1
[/test_dir1/]test_file1
[/test_dir1/]test_file2
[/test_dir1/]test_file3
[/test_dir1/]test_dir2
[/test_dir1/]test_file4
And the xml contains:
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE boost_serialization>
<boost_serialization signature="serialization::archive" version="19">
<s class_id="0" tracking_level="1" version="0" object_id="_0">
<FileObject class_id="1" tracking_level="1" version="0" object_id="_1">
<name>test_dir1</name>
<date_of_creation></date_of_creation>
<type>1</type>
</FileObject>
<num_of_contents>5</num_of_contents>
<size_of_contents>0</size_of_contents>
<contents class_id="2" tracking_level="0" version="0">
<count>5</count>
<bucket_count>13</bucket_count>
<item_version>0</item_version>
<item class_id="3" tracking_level="0" version="0">
<first>test_file4</first>
<second class_id="4" tracking_level="0" version="0">
<tx class_id="5" class_name="File" tracking_level="1" version="0" object_id="_2">
<FileObject object_id="_3">
<name>test_file4</name>
<date_of_creation></date_of_creation>
<type>0</type>
</FileObject>
<content></content>
<size>0</size>
</tx>
</second>
</item>
<item>
<first>test_dir2</first>
<second>
<tx class_id_reference="0" object_id="_4">
<FileObject object_id="_5">
<name>test_dir2</name>
<date_of_creation></date_of_creation>
<type>1</type>
</FileObject>
<num_of_contents>0</num_of_contents>
<size_of_contents>0</size_of_contents>
<contents>
<count>0</count>
<bucket_count>1</bucket_count>
<item_version>0</item_version>
</contents>
</tx>
</second>
</item>
<item>
<first>test_file3</first>
<second>
<tx class_id_reference="5" object_id="_6">
<FileObject object_id="_7">
<name>test_file3</name>
<date_of_creation></date_of_creation>
<type>0</type>
</FileObject>
<content></content>
<size>0</size>
</tx>
</second>
</item>
<item>
<first>test_file2</first>
<second>
<tx class_id_reference="5" object_id="_8">
<FileObject object_id="_9">
<name>test_file2</name>
<date_of_creation></date_of_creation>
<type>0</type>
</FileObject>
<content></content>
<size>0</size>
</tx>
</second>
</item>
<item>
<first>test_file1</first>
<second>
<tx class_id_reference="5" object_id="_10">
<FileObject object_id="_11">
<name>test_file1</name>
<date_of_creation></date_of_creation>
<type>0</type>
</FileObject>
<content></content>
<size>0</size>
</tx>
</second>
</item>
</contents>
</s>
</boost_serialization>