Why does boost::serialize not work despite everything seeming right? ("unregistered class")

1.1k views Asked by At

I am wondering about this. I have a C++ program with a number of data structs which derive from a common root and I need to serialize them using Boost. Each one has an inline member function to accept a visitor (so I can visit the structure without a "switch" statement).

The objects look like this:

In the .h file:

// Graphic component.
struct GraphicComponent : public Component {
  ... data members ...
  void accept(ComponentVisitor &vis) { vis.visitGraphicComponent(*this); }

 private:
  // Serialization routine.
  friend class boost::serialization::access;

template<class Archive>
  void serialize(Archive &a, const unsigned int v);
};
BOOST_CLASS_EXPORT_KEY(GraphicComponent)

// Position component.
struct PositionComponent : public Component {
  ... data members ...
  void accept(ComponentVisitor &vis) { vis.visitPositionComponent(*this); }

 private:
  // Serialization routine.
  friend class boost::serialization::access;

template<class Archive>
  void serialize(Archive &a, const unsigned int v);
};
BOOST_CLASS_EXPORT_KEY(PositionComponent)

...

In the .cpp file, I declare the "serialize" routines:

BOOST_CLASS_EXPORT_IMPLEMENT(GraphicComponent)
BOOST_CLASS_EXPORT_IMPLEMENT(PositionComponent)
...

template<class Archive>
  void GraphicComponent::serialize(Archive &a, const unsigned int v)
  {
    a & boost::serialization::base_object<Component>(*this);
    ... serialize data members ...
  }

template<class Archive>
  void PositionComponent::serialize(Archive &a, const unsigned int v)
  {
    a & boost::serialization::base_object<Component>(*this);
    ... serialize data members ...
  }

...

I also include the Boost archive through a common header. As far as I can tell, everything looks right. There's also a "BOOST_SERIALIZATION_ASSUME_ABSTRACT" on the base Component, as "accept" is pure virtual.

When I run the program and get to the point where it serializes this stuff, I get

 what():  unregistered class - derived class not registered or exported

Serialization occurs through a pointer to the base Component.

I've heard troubles involving Boost serialization and "libraries". The build system I was using, CMake, is set up to compile the program by assembling its subcomponents into libraries and then putting those together into a single executable to make the final program. Could that be the problem?

Also, Component derives from std::enable_shared_from_this (that's C++11 STL, not Boost) -- could this be the problem? If so, what can be done about it?

3

There are 3 answers

1
The_Sympathizer On BEST ANSWER

This is a partial answer as it doesn't explain exactly why it failed. I have managed to solve the problem by compiling the program as a single program instead of a bunch of libraries that are then statically linked together, which is how I thought I had to do it with the build system I was using since the documentation that was available online for the system was terse and when I put together the makefiles, I wasn't sure exactly how to do it. I suspect it has something to do with Boost's trouble dealing with this kind of code in libraries.

1
sehe On

In case it helps, here's a working SSCCE (or MCVE as the commenter said):

Live On Coliru

#include <boost/serialization/access.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>

struct ComponentVisitor;

struct Component {
    virtual ~Component() = default;
    virtual void accept(ComponentVisitor &v) = 0;
  private:
    // Serialization routine.
    friend class boost::serialization::access;

    template <class Archive>
    void serialize(Archive &, const unsigned int) {}
};

BOOST_SERIALIZATION_ASSUME_ABSTRACT(Component)

struct GraphicComponent;
struct PositionComponent;

struct ComponentVisitor {
    virtual void visitGraphicComponent(GraphicComponent   const &){};
    virtual void visitPositionComponent(PositionComponent const &){};
};

// Graphic component.
struct GraphicComponent : public Component {
    void accept(ComponentVisitor &vis) { vis.visitGraphicComponent(*this); }

  private:
    // Serialization routine.
    friend class boost::serialization::access;

    template <class Archive>
    void serialize(Archive &a, const unsigned int v);
};
BOOST_CLASS_EXPORT_KEY(GraphicComponent)

// Position component.
struct PositionComponent : public Component {
    void accept(ComponentVisitor &vis) { vis.visitPositionComponent(*this); }

  private:
    // Serialization routine.
    friend class boost::serialization::access;

    template <class Archive>
    void serialize(Archive &a, const unsigned int v);
};

BOOST_CLASS_EXPORT_KEY(PositionComponent)

/////////////////////////////////////////////////////

BOOST_CLASS_EXPORT_IMPLEMENT(GraphicComponent)
BOOST_CLASS_EXPORT_IMPLEMENT(PositionComponent)
//...

template <class Archive>
void GraphicComponent::serialize(Archive &a, const unsigned int)
{
    a &boost::serialization::base_object<Component>(*this);
    //... serialize data members ...
}

template <class Archive>
void PositionComponent::serialize(Archive &a, const unsigned int)
{
    a &boost::serialization::base_object<Component>(*this);
    //... serialize data members ...
}

#include <boost/make_shared.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/vector.hpp>
#include <sstream>

int main() {
    std::stringstream ss;

    {
        std::vector<boost::shared_ptr<Component> > components {
            boost::make_shared<GraphicComponent>(),
                boost::make_shared<PositionComponent>(),
                boost::make_shared<PositionComponent>(),
                boost::make_shared<GraphicComponent>(),
        };

        boost::archive::text_oarchive oa(ss);
        oa << components;
    }

    {
        std::vector<boost::shared_ptr<Component> > deserialized;

        boost::archive::text_iarchive ia(ss);
        ia >> deserialized;

        struct printer : ComponentVisitor {
            void visitPositionComponent(PositionComponent const & /*pc*/){ std::cout << __PRETTY_FUNCTION__ << "\n"; }
            void visitGraphicComponent(GraphicComponent   const & /*gc*/){ std::cout << __PRETTY_FUNCTION__ << "\n"; }
        } print;

        for (auto c : deserialized)
            c->accept(print);
    }
}

Prints

virtual void main()::printer::visitGraphicComponent(const GraphicComponent&)
virtual void main()::printer::visitPositionComponent(const PositionComponent&)
virtual void main()::printer::visitPositionComponent(const PositionComponent&)
virtual void main()::printer::visitGraphicComponent(const GraphicComponent&)

as expected

Notes

  1. Actually, since you only use the serialization in specific TUs, you could consider using non-intrusive serialization:

    Live On Coliru

    struct ComponentVisitor;
    
    struct Component {
        virtual ~Component() = default;
        virtual void accept(ComponentVisitor &v) = 0;
    };
    
    struct GraphicComponent;
    struct PositionComponent;
    
    struct ComponentVisitor {
        virtual void visitGraphicComponent(GraphicComponent   const &){};
        virtual void visitPositionComponent(PositionComponent const &){};
    };
    
    struct GraphicComponent : public Component {
        void accept(ComponentVisitor &vis) { vis.visitGraphicComponent(*this); }
    };
    
    struct PositionComponent : public Component {
        void accept(ComponentVisitor &vis) { vis.visitPositionComponent(*this); }
    };
    
    /////////////////////////////////////////////////////
    // in the CPP
    #include <boost/serialization/access.hpp>
    #include <boost/serialization/export.hpp>
    #include <boost/serialization/base_object.hpp>
    #include <boost/archive/text_iarchive.hpp>
    #include <boost/archive/text_oarchive.hpp>
    
    BOOST_SERIALIZATION_ASSUME_ABSTRACT(Component)
    BOOST_CLASS_EXPORT(GraphicComponent)
    BOOST_CLASS_EXPORT(PositionComponent)
    
    namespace boost { namespace serialization {
        template <class Archive> void serialize(Archive &, Component&, const unsigned int) {}
        template <class Archive> void serialize(Archive &a, GraphicComponent& obj, const unsigned int) {
            a &boost::serialization::base_object<Component>(obj);
        }
        template <class Archive> void serialize(Archive &a, PositionComponent& obj, const unsigned int) {
            a &boost::serialization::base_object<Component>(obj);
        }
    } }
    

    Which is considerably cleaner

  2. If you still want to access private members from inside serialize cf. e.g.

0
Skywave On

You need to include archive class headers in your library codes. https://www.boost.org/doc/libs/1_75_0/libs/serialization/doc/special.html#export

Placing BOOST_CLASS_EXPORT in library code will have no effect unless archive class headers are also included. So when building a library, one should include all headers for all the archive classes which he anticipates using.