I'm using VS12 in my project and I'm defining a very simple class which serializes its attributes. However, when I put the BOOST_CLASS_EXPORT I obtain a syntax error:
syntax error : missing ';' before '<'
However, when I omit BOOST_CLASS_EXPORT, everything works fine. I was wondering if it could depend from the template specialization used by BOOST_CLASS_EXPORT (I've used BOOST_CLASS_EXPORT_GUID as well with the same error). Is there any bug in VS12 regarding this?
The snippet is:
namespace a { namespace b {
class BaseSerialization {
public:
BaseSerialization(const std::string& functionName) : functionName(functionName) { }
virtual void getFunctionName() const = 0;
private:
friend class ::boost::serialization::access;
template <class Archive> void serialize(Archive &ar, unsigned int const file_version) {
ar & BOOST_SERIALIZATION_NVP(functionName);
}
protected:
std::string functionName;
};
class FOOZZ : BaseSerialization {
public:
FOOZZ(const std::string& functionName) : BaseSerialization("a"), functionName(functionName) { }
virtual void getFunctionName() {
int a;
}
private:
friend class ::boost::serialization::access;
template <class Archive> void serialize(Archive &ar, unsigned /*int const file_version*/) {
ar & BOOST_SERIALIZATION_NVP(functionName);
}
protected:
std::string functionName;
};
BOOST_CLASS_EXPORT_GUID(FOOZZ, "FOOZZ")
// namespace a, namespace b
Related
I haven't been able to find any docs on how to serialize a unique_ptr to an array. Any help would be great.
struct Counter{
int index;
unique_ptr<char []> name;
template<class Archive>
void serialize(Archive & archive){
archive(index, name ); // serialize things by passing them to the archive
}
};
How it is assigned.
auto buffer = std::unique_ptr<char[]>(new char[BUFFER_SIZE]);
instance.name = std::move(buffer);
You can do it, but it needs some extra work. It differs depending on the type of the archive.
For text-based archives (e.g., XMLOutputArchive/XMLInputArchive and JSONOutputArchive/JSONInputArchive) you can use the saveBinaryValue() / loadBinaryValue() (http://uscilab.github.io/cereal/assets/doxygen/classcereal_1_1JSONOutputArchive.html).
Here's a complete example:
#include <iostream>
#include <memory>
#include <cereal/archives/xml.hpp>
#include <cereal/cereal.hpp>
struct Counter
{
static constexpr std::size_t BUFFER_SIZE = 12;
int index{};
std::unique_ptr<char[]> name;
Counter() = default;
Counter(int i)
: index{i},
name{new char[BUFFER_SIZE]}
{}
template<class Archive>
void load(Archive& archive)
{
archive(index);
name.reset(new char[BUFFER_SIZE]);
archive.loadBinaryValue(name.get(), BUFFER_SIZE * sizeof(decltype(name[0])));
}
template<class Archive>
void save(Archive& archive) const
{
archive(index);
archive.saveBinaryValue(name.get(), BUFFER_SIZE * sizeof(decltype(name[0])));
}
};
int main()
{
cereal::XMLOutputArchive archive(std::cout);
Counter c(42);
archive(CEREAL_NVP(c));
}
If you are using BinaryOutputArchive / BinaryInputArchive or PortableBinaryOutputArchive / PortableBinaryInputArchive the functions become saveBinary() and loadBinary() (http://uscilab.github.io/cereal/assets/doxygen/classcereal_1_1PortableBinaryOutputArchive.html).
For those, you can also wrap your array using binary_data():
template<class Archive>
void save(Archive& archive) const
{
archive(index, cereal::binary_data(name.get(), BUFFER_SIZE));
}
The code below is my current thinking to permit boost::serialization of an immutable abstract base with virtual inheritance. I hope I missed something and there is a simpler solution...?
As it stands, it raises a few questions:
Is the comment in IObject::serialize valid?
The comment in bs::save_construct_data for House seems to indicate a bug in boost::serialization. Is that correct, or is there a better way to do this?
Is there a more elegant way to deserialise Building then the Deserialise function combined with a protected constructor?
The result of (3) is that another Building implementation will necessitate a chunk of duplicated code. I suspect this will require a bit of CRTP to mitigate - any alternatives?
How does one make this work if the virtual base contains data members? I suspect this is similar (or identical) to the Building::Deserialise.
#include <fstream>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/serialization/export.hpp>
namespace bs = boost::serialization;
// IBase comes from an external library, and we are not interested in
// serialising an IBase*.
class IBase {
public:
virtual ~IBase(){};
};
class IObject : public virtual IBase {
private:
friend class bs::access;
template <class Archive>
void serialize(Archive &ar, const unsigned int version) {
std::cout << "Called IObject's serialize\n";
// IBase contains no members so there is no need to serialise it to/from the
// archive. However, the inheritance relationship must be registered in
// boost::serialization. We cannot use base_object to do this: It will try
// to static_cast *this to IBase, but we might not have created the instance
// yet, in which case there is no virtual table and a structured exception
// will be generated.
bs::void_cast_register<IObject, IBase>(static_cast<IObject *>(nullptr),
static_cast<IBase *>(nullptr));
}
public:
virtual ~IObject() {}
};
class IBuilding : public virtual IBase {
private:
friend class bs::access;
template <class Archive>
void serialize(Archive &ar, const unsigned int version) {
std::cout << "Called IBuilding's serialize\n";
bs::void_cast_register<IBuilding, IBase>(static_cast<IBuilding *>(nullptr),
static_cast<IBase *>(nullptr));
}
public:
virtual ~IBuilding() {}
};
/* Tedious forward declarations to permit later friending. */
class Building;
class House;
namespace boost {
namespace serialization {
template <class Archive>
inline void save_construct_data(Archive &ar, const Building *t,
const unsigned int version);
template <class Archive>
inline void save_construct_data(Archive &ar, const House *t,
const unsigned int version);
template <class Archive>
inline void load_construct_data(Archive &ar, House *t,
const unsigned int version);
}
}
/* Tedious forward declarations end. */
class Building : public IBuilding, public IObject {
private:
friend class bs::access;
template <class Archive>
void serialize(Archive &ar, const unsigned int version) {
std::cout << "Called Building's serialize\n";
// We can use base_object here because although the instance might not be
// created, the memory has been allocated. Since there is no virtual
// inheritance, the static_cast can succeed.
ar &bs::make_nvp("IObject", bs::base_object<IObject>(*this));
ar &bs::make_nvp("IBuilding", bs::base_object<IBuilding>(*this));
}
template <class Archive>
inline friend void bs::save_construct_data(Archive &ar, const Building *t,
const unsigned int version);
const double weight_;
protected:
const double height_;
// The Members, associated constructor, and Deserialise facilitate recreating
// this immutable base.
struct Members {
double weight_;
double height_;
};
Building(const Members &members)
: weight_(members.weight_), height_(members.height_) {}
template <class Archive> const Members Deserialise(Archive &ar) const {
double weight;
double height;
ar >> bs::make_nvp("weight_", weight) >> bs::make_nvp("height_", height);
return {weight, height};
}
public:
bool operator==(const Building &other) const {
return weight_ == other.weight_ && height_ == other.height_;
}
virtual double Height() const = 0;
};
class House : public Building {
private:
template <class Archive>
inline friend void bs::save_construct_data(Archive &ar, const House *t,
const unsigned int version);
template <class Archive>
inline friend void bs::load_construct_data(Archive &ar, House *t,
const unsigned int version);
template <class Archive>
explicit House(Archive &ar) : Building(Deserialise(ar)) {}
public:
House(double weight, double height) : Building({weight, height}) {}
virtual double Height() const { return height_; }
};
BOOST_CLASS_EXPORT(House);
namespace boost {
namespace serialization {
template <class Archive>
inline void save_construct_data(Archive &ar, const Building *t,
const unsigned int version) {
std::cout << "Called Building's save_construct_data\n";
ar << make_nvp("weight_", t->weight_) << make_nvp("height_", t->height_);
}
template <class Archive>
inline void bs::save_construct_data(Archive &ar, const House *t,
const unsigned int version) {
std::cout << "Called House's save_construct_data\n";
const auto &base = base_object<const Building>(*t);
ar << make_nvp("Building", base);
// ar << make_nvp("Building", &base); doesn't seem to work.
// Serialising out a reference calls Building's serialize method, the
// save_construct_data is only called for a pointer. This means we
// have to call it explicitly.
save_construct_data(ar, &base, version);
}
template <class Archive>
inline void bs::load_construct_data(Archive &ar, House *t,
const unsigned int version) {
std::cout << "Called House's load_construct_data\n";
ar >> make_nvp("Building", base_object<Building>(*t));
::new (t) House{ar};
}
}
}
int main() {
const char *file_name = "house.ser";
const bool save_first = true;
const House house(45367, 2.43);
std::cout << house.Height() << "\n";
const IObject *iHouse = &house;
if (save_first) {
std::ofstream ofs(file_name);
boost::archive::xml_oarchive oa(ofs);
oa << BOOST_SERIALIZATION_NVP(iHouse);
}
IBuilding *iHouse2;
{
std::ifstream ifs(file_name);
boost::archive::xml_iarchive ia(ifs);
ia >> BOOST_SERIALIZATION_NVP(iHouse2);
}
if (dynamic_cast<const Building &>(*iHouse) ==
dynamic_cast<const Building &>(*iHouse2))
std::cout << "ok\n";
else
std::cout << "uh oh\n";
return 0;
}
I don't believe that the comment is correct. I believe that the void_cast_register is un-necessary since IBase is known to be a virtual base class of IObject.
Futhermore, if you don't add the serialization_support free functions for IBase, you won't be able to serialise/deserialise an IBase*, only an IObject* (although I think that's fine unless you are managing ownership through the IBase rather than the IObject).
I have base class User which is serializable :
class User
{
public:
User();
std::string GetLogin() const;
void SetLogin(std::string login);
protected:
std::string mLogin;
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & mLogin;
}
};
This class can be inherited by other class like this :
class UserA : public User
{
UserA();
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & boost::serialization::base_object<User>(*this);
ar & mIsSomething;
}
bool mIsSomething = true;
}
To handle those user i have a "manager" class which contain a User vector :
class Manager
{
public:
bool Add(User user);
bool Remove(unsigned int index);
private:
std::vector<User> mUsers;
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & mUsers;
}
};
So my manager can be filled with UserA , or UserB (never both at the same time). When i retrieve an element from Manager i simply cast it back to the correct child class.
This part is working fine.
But when i want to serialize the Manager class obviously Boost don't know which kind of User i'm trying to serialize and the extra fields from the child class are not serialized.
What are my solution here ?
Does my design is completly wrong ?
Should i specialize my manager class to something like this ?
class Manager
{
bool Add(UserA user);
bool Add(UserB user);
private:
std::vector<UserA> mUsersA;
std::vector<UserB> mUsersB;
}
So my manager can be filled with UserA , or UserB (never both at the same time)
No it can't:
std::vector<User> mUsers;
stores User objects by value. See What is object slicing?.
Thoughts
I'd also suggest templating the Manager on the concrete user type, but seeing how you use an actual type hierarchy, it seems you might be looking to actually use the runtime polymorphism.
Since serializing polymorphic types is somewhat more involved, let me show you a sample.
It also shows how to use e.g. boost::ptr_vector<> to manage the objects while storing them dynamically.
Live1 on Coliru
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <boost/ptr_container/serialize_ptr_vector.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/access.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/vector.hpp>
class User
{
public:
User() {};
virtual ~User() {}
std::string GetLogin() const;
void SetLogin(std::string login);
protected:
std::string mLogin;
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int /*version*/)
{
ar & mLogin;
}
};
class UserA : public User
{
public:
UserA() {};
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int /*version*/)
{
ar & boost::serialization::base_object<User>(*this);
ar & mIsSomething;
}
bool mIsSomething = true;
};
class UserB : public User
{
public:
UserB() {};
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int /*version*/)
{
ar & boost::serialization::base_object<User>(*this);
ar & mIsSomethingElse;
}
bool mIsSomethingElse = true;
};
template <typename Tag>
class UserGen : public User
{
public:
UserGen() {};
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int /*version*/)
{
ar & boost::serialization::base_object<User>(*this);
ar & mIsGen;
}
bool mIsGen = true;
};
struct GenA;
struct GenB;
struct GenC;
BOOST_CLASS_EXPORT(User)
BOOST_CLASS_EXPORT(UserA)
BOOST_CLASS_EXPORT(UserB)
BOOST_CLASS_EXPORT(UserGen<GenA>)
BOOST_CLASS_EXPORT(UserGen<GenB>)
BOOST_CLASS_EXPORT(UserGen<GenC>)
#include <boost/type_index.hpp>
class Manager
{
public:
template <typename User>
bool Add(User const& user) {
mUsers.push_back(new User(user));
return true; // FIXME?
}
bool Remove(unsigned int index) {
if (mUsers.size() > index) {
mUsers.erase(mUsers.begin()+index);
return true;
}
return false;
}
void dump() const {
for (auto& u : mUsers) {
std::cout << "user of type " << boost::typeindex::type_id_runtime(u) << "\n";
}
}
private:
boost::ptr_vector<User> mUsers;
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int /*version*/)
{
ar & mUsers;
}
};
#include <sstream>
#include <iostream>
int main() {
std::stringstream ss;
{
Manager man;
man.Add(UserA{});
man.Add(UserB{});
man.Add(UserGen<GenA>{});
man.Add(UserGen<GenB>{});
man.Add(UserGen<GenC>{});
boost::archive::text_oarchive oa(ss);
oa << man;
}
{
boost::archive::text_iarchive ia(ss);
Manager man;
ia >> man;
man.dump();
}
}
Prints
user of type UserA
user of type UserB
user of type UserGen<GenA>
user of type UserGen<GenB>
user of type UserGen<GenC>
1 linking boost 1.59 is somehow failing there :(
Thanks #m.s. for figuring out 1.58 still works
I've been assigned the following template:
#include <map>
template <typename T>
class Catalog {
struct Item {
//..
};
std::map<int, Item*> items;
public:
Catalog(void);
Catalog(const Catalog&);
~Catalog(void);
bool IsEmpty(void) const;
int Size() const;
void Add(T*);
T* Remove(T*);
T* Find(T*);
typedef void (T::*pFunc) (const T&);
void Inspection(pFunc) const;
};
Next, there is an abstract Product class and three subclasses:
class Product {
protected:
unsigned int _id;
string _name;
public:
Product(const int& id, const string& name) : _id(id), _name(name) {};
virtual void Action(const Product& p) = 0;
virtual int hashCode() {
return _id*100;
};
unsigned int getId(void) const {return _id;};
string getName(void) const {return _name;};
};
class ProductA : public Product {
public:
ProductA(const int& id, const string& name) : Product(id, name) {};
virtual void Action(const Product& p) {
cout << "ahoj" << endl;
};
};
Finally, class ProductsCatalog that handles a Catalog instance:
class ProductsCatalog {
Catalog<Product> catalog;
public:
//..
void CatalogInspection(void) const {
catalog.Inspection(&Product::Action);
}
};
What I have trouble with is the Inspection method:
template <typename T> void Catalog<T>::Inspection(pFunc p) const {
for (std::map<int, Item*>::const_iterator it=items.begin(); it!=items.end(); ++it) {
it->second->Product->*p(*(it->second->Product));
}
};
I am getting the following error:
error C2064: term does not evaluate to a function taking 1 arguments
I've tried everything I could think of, without success. The following works as intended, but is obviously not abstract enough:
it->second->Product->Action(*it->second->Product);
Did you try
(it->second->Product->*p)(*(it->second->Product));
for calling the method?
The thread Calling C++ class methods via a function pointer seems to be related.
I have the following problem when I try to call method in "TestSerialize" class during serialization process.
Here is my code:
class TestSerialize
{
public:
std::string GetVal() { return Val + "abc"; }
void SetVal(std::string tVal) { Val = tVal.substr(0, 2); }
protected:
std::string Val;
friend class boost::serialization::access;
template<class Archive> void save(Archive & ar, const unsigned int version) const
{
using boost::serialization::make_nvp;
std::string tVal = GetVal(); // Error here
ar & make_nvp("SC", tVal);
}
template<class Archive> void load(Archive & ar, const unsigned int version)
{
using boost::serialization::make_nvp;
std::string tVal;
ar & make_nvp("SC", tVal);
SetVal(tVal);
}
BOOST_SERIALIZATION_SPLIT_MEMBER();
};
int main()
{
TestSerialize tS;
std::ofstream ofs("test.xml");
boost::archive::xml_oarchive oa(ofs, boost::archive::no_header);
oa << BOOST_SERIALIZATION_NVP(tS);
ofs.close();
return 0;
}
The error that I encountered is:
'TestSerialize::GetVal' : cannot convert 'this' pointer from 'const TestSerialize' to 'TestSerialize &'
This error only happens on "save" but not "load"
I wonder why I get this error. I would like to know what Boost.Serialization do such that we have these two different behaviors.
I use Boost Library 1.47.0
save is a const function and can only call other const functions. GetVal isn't. Change it:
std::string GetVal() const { ... }