1

I want to serialize classes that have std::shared_ptr s of each other as members and that are declared and defined in different files. A minimal example of my code would be:

    //auxiliary.h
#include <memory>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/shared_ptr.hpp>



class A;
class B;
typedef std::shared_ptr< A > A_ptr;
typedef std::shared_ptr< B > B_ptr;


//A.h
#include "auxiliary.h"

class A{
  B_ptr bpt;
  void foo();
  //...
};


//A.cpp
#include "A.h"
void A::foo()
{
    //implementation
}


//B.h
#include "auxiliary.h"

class B{
  A_ptr apt;
  void bar();
  //...   
};


//B.cpp
#include "B.h"
void B::bar()
{
    //implementation
}

when I try to serialize both classes now by writing

//A.h
#include "auxiliary.h"

class A{
  template<class Archive>
  void serialize(Archive & ar, const unsigned int version)
  {
    ar & B_ptr;
  }     
  B_ptr bpt;
  void foo();
  //...
};

and

//B.h
#include "auxiliary.h"

class B{
  template<class Archive>
  void serialize(Archive & ar, const unsigned int version)
  {
    ar & A_ptr;
  }
  A_ptr apt;
  void bar();
  //...   
};

I get the error C2139 "A": an undefined class is not allowed as an argument to compiler intrinsic type __is_base_of

I understand that the compiler would rather see the serialization functions defined in the corresponding cpp files, but that won't work in the usual way as they are templates.

How could I possibly fix the whole thing?

PS.: I also read, that it helped in similar situation to instanciate the template with all the variants one uses and boost states in the manual, that this should be done somewhere via

template void serialize<boost::archive::text_iarchive>(
    boost::archive::text_iarchive & ar, 
    const unsigned int file_version
    );
template void serialize<boost::archive::text_oarchive>(
    boost::archive::text_oarchive & ar, 
    const unsigned int file_version

I've tried various ways to do it, but with no success.

SebastianZ
  • 11
  • 2

1 Answers1

0

Boost Serialization Archives are compile-time polymorphic. This means that they combine the functionality from the class and function templates that define the individual types and behaviours.

Due to the nature of templates in C++ this means that you require the full definition of these templates (and their dependent types) at the POI (point of instantiation). (See Why can templates only be implemented in the header file? for a primer).

Solution?

You can hide the serialization implementation in a TU (translation unit) that does include all the definitions.

Alternatively, you can use Polymorphic Archives. In that case, the serialize methods do not strictly need to be compile-time generic anymore.

Note: the documentation example shows the serialize member as a template function that is explicitly instantiated inside a single TU. This may be required for technical reasons¹, though logically it is completely equivalent to simply declaring two overloads taking polymorphic_[io]archive& in the header and implementing them in the same TU


¹ whether the library internals rely on T::serialize<> to be a template, rather than just letting C++ overload resolution do its job

BONUS

A demo combining a few of those ideas: Live On Wandbox

  1. auxiliary.h

    #pragma once
    
    class A;
    class B;
    
    #include <memory>
    
    typedef std::shared_ptr<A> A_ptr;
    typedef std::shared_ptr<B> B_ptr;
    
    namespace boost { namespace serialization { class access; } }
    
  2. A.h

    #pragma once
    #include "auxiliary.h"
    
    class A {
      public:
        B_ptr bpt;
        void foo();
    
      private:
        friend class boost::serialization::access;
        template <class Archive> void serialize(Archive&, unsigned);
    };
    
  3. B.h

    #pragma once
    #include "auxiliary.h"
    
    class B {
      public:
        A_ptr apt;
        void bar();
        //...
      private:
        friend class boost::serialization::access;
        template <class Archive> void serialize(Archive&, unsigned);
    };
    
  4. A.cpp

    // A.cpp
    #include "A.h"
    
    #include "B.h"
    void A::foo() {
        // implementation
        bpt = std::make_shared<B>();
    }
    
    template <class Archive>
    void A::serialize(Archive &ar, unsigned) 
    {
        ar & bpt; 
    }
    
    #include <boost/archive/text_iarchive.hpp>
    #include <boost/archive/text_oarchive.hpp>
    #include <boost/serialization/shared_ptr.hpp>
    #include "B.h" // required at point of instatiation
    template void A::serialize(boost::archive::text_iarchive&, unsigned);
    template void A::serialize(boost::archive::text_oarchive&, unsigned);
    
  5. B.cpp

    // B.cpp
    #include "B.h"
    
    #include <iostream>
    void B::bar() {
        // implementation
        std::cout << "Hello from B::bar()\n";
    }
    
    template <class Archive>
    void B::serialize(Archive &ar, unsigned) 
    {
        ar & apt; 
    }
    
    #include <boost/archive/text_iarchive.hpp>
    #include <boost/archive/text_oarchive.hpp>
    #include <boost/serialization/shared_ptr.hpp>
    #include "A.h" // required at point of instatiation
    template void B::serialize(boost::archive::text_iarchive&, unsigned);
    template void B::serialize(boost::archive::text_oarchive&, unsigned);
    
  6. test.cpp

    #include <iostream>
    #include <sstream>
    
    void test_serialize(std::ostream&);
    void test_deserialize(std::istream&);
    
    int main() {
        std::stringstream ss;
    
        test_serialize(ss);
    
        std::cout << ss.str() << std::flush;
    
        test_deserialize(ss);
    }
    
    #include "auxiliary.h"
    #include <boost/archive/text_oarchive.hpp>
    #include "A.h"
    //#include "B.h" // optional, see below
    void test_serialize(std::ostream& os) {
        boost::archive::text_oarchive oa(os);
    
        A a1, a2;
        a1.foo();
        // a1.bpt->bar(); // only works if B.h included
        A a3 = a1; // copy, should alias a1.bpt and a3.bpt
        oa << a1 << a2 << a3;
    }
    
    #include <boost/archive/text_iarchive.hpp>
    #include "B.h" // optional, see below
    void test_deserialize(std::istream& is) {
        boost::archive::text_iarchive ia(is);
    
        A a1, a2, a3;
        ia >> a1 >> a2 >> a3;
    
        std::cout << std::boolalpha;
        std::cout << "B correctly deserialized: " << (a1.bpt && !a2.bpt) << "\n";
        std::cout << "Correctly aliased a1.bpt == a3.bpt: " << (a1.bpt == a3.bpt) << "\n";
    
        a3.bpt->bar(); // only works if B.h included
    }
    

Prints

/home/sehe/custom/boost_1_65_0/boost/archive/detail/oserializer.hpp:467:22: runtime error: reference binding to null pointer of type 'const struct A'
/home/sehe/custom/boost_1_65_0/boost/archive/detail/oserializer.hpp:467:22: runtime error: reference binding to null pointer of type 'const struct B'
22 serialization::archive 15 1 0
0 0 1 2 1 0
1 0 1 -1
2 -1
3 2 1
/home/sehe/custom/boost_1_65_0/boost/archive/detail/iserializer.hpp:540:19: runtime error: reference binding to null pointer of type 'struct B'
/home/sehe/custom/boost_1_65_0/boost/archive/detail/iserializer.hpp:541:67: runtime error: reference binding to null pointer of type 'const struct B'
B correctly deserialized: true
Correctly aliased a1.bpt == a3.bpt: true
Hello from B::bar()
sehe
  • 374,641
  • 47
  • 450
  • 633