Boost polymorphic serialization cause "unregistered class" with registered classes

109 views Asked by At

Serialization with boost for polymorphic classes does not seem to work.

I have a virtual class (cf. Base) and a child class (cf. Derived), I have tried several methods to register them (cf. BOOST_CLASS_EXPORT_GUID, BOOST_CLASS_EXPORT, BOOST_SERIALIZATION_ASSUME_ABSTRACT, BOOST_CLASS_EXPORT_KEY and BOOST_CLASS_EXPORT_IMPLEMENT) but none of them seem to work because I get the error unregistered class - derived class not registered or exported every time.

This only works if I register the classes in the .hpp files. However I can't do this because otherwise it only works if my classes are only included once.

Minimal code example :

  • base.hpp
#pragma once

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

struct Base
{
    virtual ~Base() = default;

    template <class Archive>
    void serialize(Archive & ar, unsigned /*version*/)
    {
        ar & base_value;
    }

    int base_value;
};
  • base.cpp
#include "base.hpp"

BOOST_CLASS_EXPORT(Base)
  • derived.hpp
#include "base.hpp"

struct Derived : public Base
{
    template <class Archive>
    void serialize(Archive & ar, unsigned /*version*/)
    {
        ar & boost::serialization::base_object<Base>(*this);
        ar & derived_value;
    }

    int derived_value;
};
  • derived.cpp
#include "derived.hpp"

BOOST_CLASS_EXPORT(Derived)
  • main.cpp
#include <boost/archive/text_oarchive.hpp>

#include "base.hpp"
#include "derived.hpp"

int main()
{
    std::shared_ptr<Base> base = std::make_shared<Derived>();

    std::ostringstream output_stream;
    boost::archive::text_oarchive archive(output_stream);
    archive << base; // unregistered class - derived class not registered or exported

    return 0;
}

UPDATE 1 :

It seems to work when I compile my code as shared library, linked statically against boost. And it don’t work when I compile my code as static library.

PS : My library is then linked dynamically against my test executable.

  • CMakeLists.txt (library)
set(mylib ${CMAKE_PROJECT_NAME}_lib)

file(GLOB_RECURSE sources LIST_DIRECTORIES true *.hpp *.cpp)

set(sources ${sources})

include_directories(${CMAKE_SOURCE_DIR}/include)

add_library(${mylib} SHARED ${sources}) # don't work when set as STATIC

target_link_libraries(${mylib}

    boost_system
    boost_serialization
)

# more target_link_libraries calls
  • CMakeLists.txt (test)
set(mytest ${CMAKE_PROJECT_NAME}_test)

file(GLOB_RECURSE sources LIST_DIRECTORIES true *.hpp *.cpp)

set(sources ${sources})

include_directories(${CMAKE_SOURCE_DIR}/include)

add_executable(${mytest} ${sources})

add_test(NAME ${mytest} COMMAND ${mytest})

target_link_libraries(${mytest} PRIVATE ${CMAKE_PROJECT_NAME}_lib)

target_link_libraries(${mytest} PRIVATE

    gtest
)
1

There are 1 answers

0
sehe On

You claim the classes are registered:

Boost polymorphic serialization cause "unregistered class" with registered classes

However, they are not, in fact, registered. The only place where they are is inside the TU's base.cpp and derived.hpp, and they have no external visibility, like in main.cpp.

There's a chance that it will "work" in certain situations due to SIOF. But why rely on that. It doesn't work on my system: https://i.stack.imgur.com/EKFb0.jpg

You should split the EXPORT declaration and definitions, as intended:

BOOST_CLASS_EXPORT_KEY(Base) // inside base.hpp

and

BOOST_CLASS_EXPORT_IMPLEMENT(Base) // inside base.cpp

Additionally, heed the documentation warning:

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. Alternatively, one can include headers for just the Polymoprhic Archives.

Finally (and this took me some sweet time to figure out...) you need to help CMake realize that your STATIC library is a collection of object files to make it also consider weak symbols for linkage:

nm -C build/lib/libElyas_lib.a  | grep mutable
0000000000000000 W boost::serialization::singleton<boost::archive::detail::iserializer<boost::archive::text_iarchive, Base> >::get_mutable_instance()
0000000000000000 W boost::serialization::singleton<boost::archive::detail::oserializer<boost::archive::text_oarchive, Base> >::get_mutable_instance()
0000000000000000 W boost::serialization::singleton<boost::archive::detail::extra_detail::guid_initializer<Base> >::get_mutable_instance()
0000000000000000 W boost::serialization::singleton<boost::archive::detail::iserializer<boost::archive::text_iarchive, Derived> >::get_mutable_instance()
0000000000000000 W boost::serialization::singleton<boost::archive::detail::oserializer<boost::archive::text_oarchive, Derived> >::get_mutable_instance()
0000000000000000 W boost::serialization::singleton<boost::archive::detail::extra_detail::guid_initializer<Derived> >::get_mutable_instance()

With

add_library(${mylib} OBJECT ${sources})

these link okay!

Fixed Version

  • File CMakeLists.txt

     project(Elyas)
     cmake_minimum_required(VERSION 3.22)
    
     set(CMAKE_CXX_FLAGS "-Wall -Wextra -pedantic -Wconversion")
    
     add_subdirectory(lib)
     add_subdirectory(test)
    
  • File include/base.hpp

     #pragma once
    
     #include <boost/archive/text_oarchive.hpp>
     #include <boost/archive/text_iarchive.hpp>
     #include <boost/serialization/shared_ptr.hpp>
     #include <boost/serialization/base_object.hpp>
     #include <boost/serialization/export.hpp>
    
     struct Base
     {
         virtual ~Base() = default;
    
         template <class Archive>
         void serialize(Archive & ar, unsigned /*version*/)
         {
             ar & base_value;
         }
    
         int base_value;
     };
    
     BOOST_CLASS_EXPORT_KEY(Base)
    
  • File include/derived.hpp

     #pragma once
     #include "base.hpp"
    
     struct Derived : public Base
     {
         template <class Archive>
         void serialize(Archive & ar, unsigned /*version*/)
         {
             ar & boost::serialization::base_object<Base>(*this);
             ar & derived_value;
         }
    
         int derived_value;
     };
    
     BOOST_CLASS_EXPORT_KEY(Derived)
    
  • File lib/CMakeLists.txt

     set(mylib ${CMAKE_PROJECT_NAME}_lib)
     set(sources base.cpp derived.cpp)
    
     include_directories(${CMAKE_SOURCE_DIR}/include)
    
     add_library(${mylib} OBJECT ${sources})
     target_link_libraries(${mylib}
         boost_system
         boost_serialization
     )
    
  • File lib/base.cpp

     #include "base.hpp"
    
     BOOST_CLASS_EXPORT_IMPLEMENT(Base)
    
     /*
      *namespace boost { namespace archive { namespace detail { namespace extra_detail {
      *    template <> struct init_guid<Base> {
      *        static guid_initializer<Base> const& g;
      *    };
      *    guid_initializer<Base> const& init_guid<Base>::g =
      *        ::boost ::serialization ::singleton<guid_initializer<Base>>::get_mutable_instance()
      *            .export_guid();
      *}}}} // namespace boost::archive::detail::extra_detail
      */
    
  • File lib/derived.cpp

     #include "derived.hpp"
    
     BOOST_CLASS_EXPORT_IMPLEMENT(Derived)
    
  • File test/CMakeLists.txt

     set(mytest ${CMAKE_PROJECT_NAME}_test)
    
     set(sources main.cpp)
    
     include_directories(${CMAKE_SOURCE_DIR}/include)
    
     add_executable(${mytest} ${sources})
    
     target_link_libraries(${mytest} PRIVATE ${CMAKE_PROJECT_NAME}_lib)
    
     target_link_libraries(${mytest} PRIVATE
         gtest
     )
    
  • File test/main.cpp

     #include "derived.hpp"
    
     int main() {
         std::shared_ptr<Base> base = std::make_shared<Derived>();
    
         std::ostringstream output_stream;
         boost::archive::text_oarchive archive(output_stream);
         archive << base;
     }
    

See it on Github: https://github.com/sehe/so-q75452526

enter image description here

Summary / Notes

The linked documentation page contains advanced examples and recommendations for shipping serialization support in DLL/static libraries. They come with examples and tests in the library repository, so they may be of help (although likely not CMake specific)