Reputation: 135
I have created a map of multiple different data types. s64, f64, Arrays, Images, etc.
To do so, i used a map of type std::map<std::string, std::unique_ptr<MapEntryInterface>> database;
.
I want to store it, and reload it from filesystem. But i heard that maps cant be sored in a one-liner. So i tried to store the data part std::unique_ptr<MapEntryInterface> test;
of one pair first:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive& ar, unsigned int version) {
ar & test;
}
The program crashes at ar & test
throwing an exception: "unregistered class - derived class not registered or exported"
.
whats the issue? i dont understand it.
here is the minimal code: (deleted)
#include <boost/serialization/vector.hpp>
//....
};
as 463035818_is_not_a_number pointed out, my sniped is not working.
i recreated it, and got a lot further i think. But as soon as i insert the load from file function, it does not compile anymore saying:
error C2280: "std::pair<const _Kty,_Ty>::pair(const std::pair<const _Kty,_Ty> &)" : Es wurde versucht, auf eine gelöschte Funktion zu verweisen
#include <boost/serialization/vector.hpp>
#include <boost/serialization/unique_ptr.hpp>
#include <boost/serialization/map.hpp>
#include <boost/serialization/utility.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <fstream>
#include <iostream>
class MapEntryInterface {
public:
MapEntryInterface(std::string type_str) : type(type_str) {}
std::string type;
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive& ar, unsigned int version) {
if (type == "s64") {
MapEntryS64* cast = (MapEntryS64*)this;
cast->serialize(ar, version);
}
if (type == "f64") {
MapEntryF64* cast = (MapEntryF64*)this;
cast->serialize(ar, version);
}
}
};
class MapEntryS64 : public MapEntryInterface {
public:
MapEntryS64(int init_val, const std::string& type_str)
: data(init_val), MapEntryInterface(type_str)
{}
uint64_t data;
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive& ar, unsigned int version) {
ar & type;
ar & data;
}
};
class MapEntryF64 : public MapEntryInterface {
public:
MapEntryF64(double init_val, const std::string& type_str)
: data(init_val), MapEntryInterface(type_str)
{}
double data;
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive& ar, unsigned int version) {
ar & type;
ar & data;
}
};
class MapDataBase {
public:
MapDataBase()
//: test(std::unique_ptr<MapEntryInterface>(new MapEntryS64(381, "s64")))
{
database["key1"] = std::unique_ptr<MapEntryInterface>(new MapEntryS64(381, "s64"));
database["key2"] = std::unique_ptr<MapEntryInterface>(new MapEntryF64(3.124512, "f64"));
};
bool SaveToFile() {
std::ofstream ofs("boost_export.dat");
if (ofs.is_open()) {
boost::archive::text_oarchive oa(ofs);
oa & *this;
return true;
}
return false;
}
bool loadFromFile() {
std::ifstream ifs("boost_export.dat");
if (ifs.is_open())
{
try
{
boost::archive::text_iarchive ia(ifs);
ia & *this;
//std::string yolo;
//ia >> yolo;
//ia >> bam;
}
catch (std::exception& ex)
{
std::cout << ex.what() << std::endl;
return false;
}
}
return true;
}
private:
std::map<std::string, std::unique_ptr<MapEntryInterface>> database;
//std::unique_ptr<MapEntryInterface> test;
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive& ar, unsigned int version) {
ar & database;
}
};
void main() {
MapDataBase tmp;
tmp.SaveToFile();
MapDataBase tmp2;
//tmp2.loadFromFile();
}
Upvotes: 1
Views: 297
Reputation: 393739
I spent a large amount of time making things self-contained. Among the many changes:
you should not have the base class serializing the derived (that's classic OOP), instead Boost expects derived classes to serialize their base_object<>
(allowing static polymorphism and, incidentally, type registration)
of course, the base class should serialize its data members (type
)
the base class SHOULD have a virtual destructor (otherwise deleting through unique_ptr
's destructor will be unspecified
Up till here:
class MapEntryInterface { public: virtual ~MapEntryInterface() = default; protected: MapEntryInterface(std::string type_str) : type_(type_str) {} std::string type_; friend class boost::serialization::access; template <class Ar> void serialize(Ar& ar, unsigned) { ar& type_; } }; using EntryPtr = std::unique_ptr<MapEntryInterface>;
The base/member initialize lists are in misleading order; initialization happens in declaration order anyways
The type_str
is a code smell: the whole idea of OOP virtualization is not to have switching on types everywhere. I went half-way for you by at least defaulting the values, but you could probably do without it entirely. After all the type is the type.
Now add the base_object
serialization:
template <class Ar> void serialize(Ar& ar, unsigned)
{
ar& boost::serialization::base_object<MapEntryInterface>(*this);
ar& data_;
}
MapDatabase
benefits from several simplifications
new
or delete
loadFromFile
has no useful way of handling the exception, re-throw, or just let is escape,MapDatabase
the return type instead of bool
.saveToFile
and loadFromFile
should probably take a filename parameter
Not shown: arguably saveToFile
and loadFromFile
need not be part of MapDatabase
At this point, adding a little bit of code to print database contents:
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/serialization/map.hpp>
#include <boost/serialization/unique_ptr.hpp>
#include <boost/serialization/utility.hpp>
#include <boost/serialization/vector.hpp>
#include <fstream>
#include <iostream>
class MapEntryInterface {
public:
virtual ~MapEntryInterface() = default;
virtual void print(std::ostream&) const = 0;
protected:
MapEntryInterface(std::string type_str) : type_(type_str) {}
std::string type_;
friend class boost::serialization::access;
template <class Ar> void serialize(Ar& ar, unsigned) { ar& type_; }
friend std::ostream& operator<<(std::ostream& os, MapEntryInterface const& e) {
e.print(os);
return os;
}
};
using EntryPtr = std::unique_ptr<MapEntryInterface>;
class MapEntryS64 : public MapEntryInterface {
public:
MapEntryS64(int init_val = 0, const std::string& type_str = "s64")
: MapEntryInterface(type_str)
, data_(init_val)
{
}
private:
uint64_t data_;
friend class boost::serialization::access;
template <class Ar> void serialize(Ar& ar, unsigned) {
ar& boost::serialization::base_object<MapEntryInterface>(*this);
ar& data_;
}
virtual void print(std::ostream& os) const override {
os << "S64(" << data_ << ", " << std::quoted(type_) << ")";
}
};
class MapEntryF64 : public MapEntryInterface {
public:
MapEntryF64(double init_val = 0, const std::string& type_str = "f64")
: MapEntryInterface(type_str)
, data_(init_val)
{
}
private:
double data_;
friend class boost::serialization::access;
template <class Ar> void serialize(Ar& ar, unsigned)
{
ar& boost::serialization::base_object<MapEntryInterface>(*this);
ar& data_;
}
virtual void print(std::ostream& os) const override {
os << "F64(" << data_ << ", " << std::quoted(type_) << ")";
}
};
class MapDatabase {
public:
using Map = std::map<std::string, EntryPtr>;
MapDatabase() {
database_.emplace("key1", std::make_unique<MapEntryS64>(381));
database_.emplace("key2", std::make_unique<MapEntryF64>(3.124512));
};
bool SaveToFile(std::string const& filename) const
{
try {
std::ofstream ofs(filename, std::ios::binary);
boost::archive::text_oarchive oa(ofs);
oa << *this;
return true;
} catch (std::exception& ex) {
std::cout << ex.what() << std::endl;
return false;
}
}
static MapDatabase loadFromFile(std::string const& filename)
{
MapDatabase db;
std::ifstream ifs(filename, std::ios::binary);
boost::archive::text_iarchive ia(ifs);
ia >> db;
return db;
}
friend std::ostream& operator<<(std::ostream& os, MapDatabase const& mdb)
{
for (auto const& [k, b] : mdb.database_)
if (b) os << std::quoted(k) << " -> " << *b << "\n";
else os << std::quoted(k) << " -> NULL\n";
return os;
}
private:
Map database_;
friend class boost::serialization::access;
template <class Ar> void serialize(Ar& ar, unsigned) { ar& database_; }
};
int main() {
{
MapDatabase tmp;
std::cout << "------ tmp:\n" << tmp << "\n";
if (not tmp.SaveToFile("boost_export.dat"))
return 1;
}
MapDatabase roundtrip = MapDatabase::loadFromFile("boost_export.dat");
std::cout << "------ roundtrip:\n" << roundtrip << "\n";
}
Prints
------ tmp:
"key1" -> S64(381, "s64")
"key2" -> F64(3.12451, "f64")
unregistered class - derived class not registered or exported
unregistered class
runtime errorThe message says it all: at time of deserialization, there is no information about the types that can be deserialized. Add that:
#include <boost/serialization/export.hpp>
BOOST_CLASS_EXPORT(MapEntryF64)
BOOST_CLASS_EXPORT(MapEntryS64)
Now it prints
------ tmp:
"key1" -> S64(381, "s64")
"key2" -> F64(3.12451, "f64")
------ roundtrip:
"key1" -> S64(381, "s64")
"key2" -> F64(3.12451, "f64")
In the case of separate translation units, split the class export macros as per the documentation. E.g. the class export macro ends up doing
#define BOOST_CLASS_EXPORT_GUID(T, K) \
BOOST_CLASS_EXPORT_KEY2(T, K) \
BOOST_CLASS_EXPORT_IMPLEMENT(T) \
/**/
So, naively (taking another half-hour to split it all op sensibly:)
File test.cpp
#include "MapDatabase.h"
#include <iostream>
int main() {
{
MapDatabase tmp;
std::cout << "------ tmp:\n" << tmp << "\n";
if (not tmp.SaveToFile("boost_export.dat"))
return 1;
}
MapDatabase roundtrip = MapDatabase::loadFromFile("boost_export.dat");
std::cout << "------ roundtrip:\n" << roundtrip << "\n";
}
File MapDatabase.h
#pragma once
#include "MapEntryS64.h"
#include "MapEntryF64.h"
#include <boost/serialization/map.hpp>
#include <boost/serialization/unique_ptr.hpp>
class MapDatabase {
public:
using Map = std::map<std::string, EntryPtr>;
MapDatabase();
bool SaveToFile(std::string const& filename) const;
static MapDatabase loadFromFile(std::string const& filename);
friend std::ostream& operator<<(std::ostream&, MapDatabase const&);
private:
Map database_;
friend class boost::serialization::access;
template <class Ar> void serialize(Ar& ar, unsigned) { ar& database_; }
};
File MapDatabase.cpp
#include "MapDatabase.h"
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <fstream>
#include <iostream>
MapDatabase::MapDatabase() {
database_.emplace("key1", std::make_unique<MapEntryS64>(381));
database_.emplace("key2", std::make_unique<MapEntryF64>(3.124512));
}
bool MapDatabase::SaveToFile(std::string const& filename) const
{
try {
std::ofstream ofs(filename, std::ios::binary);
boost::archive::text_oarchive oa(ofs);
oa << *this;
return true;
} catch (std::exception& ex) {
std::cout << ex.what() << std::endl;
return false;
}
}
MapDatabase MapDatabase::loadFromFile(std::string const& filename)
{
MapDatabase db;
std::ifstream ifs(filename, std::ios::binary);
boost::archive::text_iarchive ia(ifs);
ia >> db;
return db;
}
std::ostream& operator<<(std::ostream& os, MapDatabase const& mdb)
{
for (auto const& [k, b] : mdb.database_)
if (b) os << std::quoted(k) << " -> " << *b << "\n";
else os << std::quoted(k) << " -> NULL\n";
return os;
}
File MapEntryF64.h
#pragma once
#include "MapEntryInterface.h"
#include <boost/serialization/base_object.hpp>
class MapEntryF64 : public MapEntryInterface {
public:
MapEntryF64(int init_val = 0, const std::string& type_str = "f64");
private:
uint64_t data_;
friend class boost::serialization::access;
template <class Ar> void serialize(Ar& ar, unsigned) {
ar& boost::serialization::base_object<MapEntryInterface>(*this);
ar& data_;
}
virtual void print(std::ostream& os) const override;
};
#include <boost/serialization/export.hpp>
BOOST_CLASS_EXPORT_KEY(MapEntryF64)
File MapEntryInterface.h
#pragma once
#include <iosfwd>
#include <string>
#include <memory>
#include <boost/serialization/access.hpp>
class MapEntryInterface {
public:
virtual ~MapEntryInterface() = default;
virtual void print(std::ostream&) const = 0;
protected:
MapEntryInterface(std::string type_str);
std::string type_;
friend class boost::serialization::access;
template <class Ar> void serialize(Ar& ar, unsigned) { ar& type_; }
friend std::ostream& operator<<(std::ostream&, MapEntryInterface const&);
};
using EntryPtr = std::unique_ptr<MapEntryInterface>;
File MapEntryS64.h
#pragma once
#include "MapEntryInterface.h"
#include <boost/serialization/base_object.hpp>
class MapEntryS64 : public MapEntryInterface {
public:
MapEntryS64(int init_val = 0, const std::string& type_str = "s64");
private:
uint64_t data_;
friend class boost::serialization::access;
template <class Ar> void serialize(Ar& ar, unsigned) {
ar& boost::serialization::base_object<MapEntryInterface>(*this);
ar& data_;
}
virtual void print(std::ostream& os) const override;
};
#include <boost/serialization/export.hpp>
BOOST_CLASS_EXPORT_KEY(MapEntryS64)
File MapEntryF64.cpp
#include "MapEntryF64.h"
#include <ostream>
#include <iomanip>
MapEntryF64::MapEntryF64(int init_val, const std::string& type_str)
: MapEntryInterface(type_str)
, data_(init_val)
{
}
void MapEntryF64::print(std::ostream& os) const {
os << "F64(" << data_ << ", " << std::quoted(type_) << ")";
}
BOOST_CLASS_EXPORT_IMPLEMENT(MapEntryF64)
File MapEntryInterface.cpp
#include "MapEntryInterface.h"
MapEntryInterface::MapEntryInterface(std::string type_str) : type_(type_str) {}
std::ostream& operator<<(std::ostream& os, MapEntryInterface const& e)
{
e.print(os);
return os;
}
File MapEntryS64.cpp
#include "MapEntryS64.h"
#include <ostream>
#include <iomanip>
MapEntryS64::MapEntryS64(int init_val, const std::string& type_str)
: MapEntryInterface(type_str)
, data_(init_val)
{
}
void MapEntryS64::print(std::ostream& os) const {
os << "S64(" << data_ << ", " << std::quoted(type_) << ")";
}
BOOST_CLASS_EXPORT_IMPLEMENT(MapEntryS64)
Prints: Live On Wandbox
------ tmp:
"key1" -> S64(381, "s64")
"key2" -> F64(3, "f64")
unregistered class - derived class not registered or exported
Not at all. Just need to read the documentation closely:
BOOST_CLASS_EXPORT
in the same source module that includes any of the archive class headers will instantiate code required to serialize polymorphic pointers of the indicated type to the all those archive classes. If no archive class headers are included, then no code will be instantiated.
So, adding the includes:
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
BOOST_CLASS_EXPORT_IMPLEMENT(MapEntryF64)
(and the same for MapEntryS64
):
------ tmp:
"key1" -> S64(381, "s64")
"key2" -> F64(3, "f64")
------ roundtrip:
"key1" -> S64(381, "s64")
"key2" -> F64(3, "f64")
I prefer simplicity. I would probably replace all of the above with:
File MapDatabase.h
#pragma once
#include <map>
#include <boost/variant.hpp>
namespace Database {
struct Nil { void serialize(auto&, unsigned) {} };
using S64 = uint64_t;
using F64 = double;
using Entry = boost::variant<Nil, S64, F64>;
using Map = std::map<std::string, Entry>;
std::string_view typeOf(Entry const&);
void SaveToFile(std::string const& filename, Map const& m);
[[nodiscard]] Map loadFromFile(std::string const& filename);
std::ostream& operator<<(std::ostream&, Nil);
std::ostream& operator<<(std::ostream&, Map const&);
} // namespace Database
File MapDatabase.cpp
#include "MapDatabase.h"
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/serialization/map.hpp>
#include <boost/serialization/variant.hpp>
#include <fstream>
#include <iomanip>
#include <iostream>
namespace Database {
std::string_view typeOf(Entry const& e) {
assert(e.which() < 3);
return std::array{"Nil", "S64", "F64"}[e.which()];
}
void SaveToFile(std::string const& filename, Map const& m)
{
std::ofstream ofs(filename, std::ios::binary);
boost::archive::text_oarchive oa(ofs);
oa << m;
}
Map loadFromFile(std::string const& filename)
{
Map db;
std::ifstream ifs(filename, std::ios::binary);
boost::archive::text_iarchive ia(ifs);
ia >> db;
return db;
}
std::ostream& operator<<(std::ostream& os, Nil) { return os << "NULL"; }
std::ostream& operator<<(std::ostream& os, Map const& m)
{
for (auto const& [k, v] : m)
os << typeOf(v) << "\t" << std::quoted(k) << " -> " << v << "\n";
return os;
}
} // namespace Database
File test.cpp
#include "MapDatabase.h"
#include <iostream>
int main() {
SaveToFile("boost_export.dat",
Database::Map{
{"key1", 381ul},
{"key3", {}},
{"key2", 3.124512},
});
std::cout << Database::loadFromFile("boost_export.dat");
}
Printing Live On Wandbox
S64 "key1" -> 381
F64 "key2" -> 3.12451
Nil "key3" -> NULL
Upvotes: 1