1

I try to serialize a class, say B (in file b.h), which is derived from another one, say A (in file a.h). Both classes have private members and I want to serialize both with the boost serialization library non-intrusively. The serialization/deserialization of A does work so far. For the same for the derived class one would use

ar & boost::serialization::base_object<base_class>(*this);

when the intrusive method is used, but where to put it in the non-intrusive case (save/load/serialize or all three?)? And what object has to been used in place of the this pointer?

In the productive code I have derived class a bit more complicated than B. There I got a compiler error which I wasn't able to reproduce in this small example. The compiler message (MSVC 2015, C2665, translated in English):

'boost::serialization::save' : none of the number1 overloads can convert parameter number2 from type 'type'

The Error in German:

Fehler C2665 "boost::serialization::save": Durch keine der 3 Überladungen konnten alle Argumenttypen konvertiert werden. CalorCLI c:\boost_1_61_0\boost\serialization\split_free.hpp 45

Could anyone help?

The Code of a.h :

#pragma once

class A {
private:
    int elemA;

public:
    A() = default;
    A(int elem) : elemA(elem) {};
    virtual ~A() = default;

    int getElemA() const { return elemA; }
    void setElemA(int elem) { 
        elemA = elem; 
    }

};

The code of b.h :

#pragma once
#include "a.h"

class B : public A {
private:
    int elemB;

public:
    B() = default;
    B(int elem) : elemB(elem) {};
    virtual ~B() = default;

    int getElemB() const { return elemB; }
    void setElemB(int elem) { elemB = elem; }

};

The Code of the main program:

// TestSerialization.cpp : Definiert den Einstiegspunkt für die Konsolenanwendung.
//
#include <string>
#include <fstream>
#include <iostream>

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include "b.h"
#include "stdafx.h"

namespace boost {
    namespace serialization {

        template<class Archive>
        void save(Archive & ar, const A & pA, const unsigned int version)
        {
            ar & pA.getElemA();
        }

        template<class Archive>
        void load(Archive & ar, A & pA, const unsigned int version)
        {
            int n;
            ar & n; 
            pA.setElemA(n); 
        }

        template<class Archive>
        void serialize(Archive & ar, A & pA, const unsigned int version)
        {
            boost::serialization::split_free(ar, pA, version);
        }

        template<class Archive>
        void save(Archive & ar, const B & pB, const unsigned int version)
        {
            ar & pB.getElemB();
        }

        template<class Archive>
        void load(Archive & ar, B & pB, const unsigned int version)
        {
            int n;
            ar & n;
            pB.setElemB(n);
        }

        template<class Archive>
        void serialize(Archive & ar, B & pB, const unsigned int version)
        {
            boost::serialization::split_free(ar, pB, version);
        }
    }
}

int main()
{
    A *objA= new A(747);
    {
        std::ofstream ofs("SavedA");
        boost::archive::text_oarchive oa(ofs);
        oa << objA;
    }

    {
        A *objA1 = new A();
        std::ifstream ifs("SavedA");
        boost::archive::text_iarchive ia(ifs);
        ia >> objA1;
    }

    B *objB = new B(747);
    {
        std::ofstream ofs("SavedB");
        boost::archive::text_oarchive oa(ofs);
        oa << objB;
    }

    {
        B *objB1 = new B();
        std::ifstream ifs("SavedB");
        boost::archive::text_iarchive ia(ifs);
        ia >> objB1;
    }

    return 0;
}
Anton
  • 3,113
  • 14
  • 12
Robert
  • 97
  • 6
  • I changed the constructor in b.h to
     B(int elem1, int elem) : A(elem1), elemB(elem) {};
    and inserted
     ar & boost::serialization::base_object(pB);
    in the serialize method of B. This works. The problem with Error 2665 in the productive code remains.
    – Robert Aug 26 '17 at 09:35
  • @BeyelerStudios The [documentation](http://www.boost.org/doc/libs/1_46_1/libs/serialization/doc/serialization.html#base) specifically warns against this: *"Resist the temptation to just cast *this to the base class. This might seem to work but may fail to invoke code necessary for proper serialization."* – Anton Aug 26 '17 at 11:12
  • Fix formatting of your code please. Use backquotes ( ` ) for "inline" code and four space indent with a blank line before for code blocks. Do not use html escapes and the like for code here, and edit and remove the ones you added. – Yakk - Adam Nevraumont Aug 26 '17 at 11:15
  • @BeyelerStudios In the next paragraph they say that the recommended method is also different from calling `serialize` directly (which I suppose also applies to calling `save`): *Note that this is NOT the same as calling the serialize function of the base class. This might seem to work but will circumvent certain code used for tracking of objects, and registering base-derived relationships and other bookkeeping that is required for the serialization system to function as designed.* – Anton Aug 26 '17 at 12:15
  • 1
    @BeyelerStudios In particular, registering base-derived relationships seems to be necessary for properly serializing `shared_ptr`'s. – Anton Aug 26 '17 at 12:16

2 Answers2

0

First, a fair warning about Quasi-Classes (PDF). They are the enemy of encapsulation and confuse OOP.

Next, let me answer two of your questions real quick and proceed to show my take on this:

  1. Q. where to put it in the non-intrusive case (save/load/serialize or all three?)?

    Either in serialize OR in both save and load (if you have split implementations)

  2. Q. what object has to been used in place of the this pointer?

    The same object. If you do member-function serialize this points to the same object as gets passed the free function as the second argument. Just use that object.

My Take

Now, let me refer to my answer to Get private data members for non intrusive boost serialization C++

Here's a demonstration of the idea Tanner suggested in his comment

Live On WandBox

  • a.h

    #pragma once
    
    class A {
    private:
        int elemA;
    
    public:
        A(int elem = 0) : elemA(elem) {};
        virtual ~A() = default;
    
        int getElemA() const { return elemA; }
        void setElemA(int elem) { elemA = elem; }
    };
    
  • b.h

    #pragma once
    #include "a.h"
    
    class B : public A {
    private:
        int elemB;
    
    public:
        B(int elem = 0) : A(42), elemB(elem) {};
    
        int getElemB() const { return elemB; }
        void setElemB(int elem) { elemB = elem; }
    };
    
  • main.cpp

    #include <string>
    #include <sstream>
    #include <iostream>
    
    #include <boost/archive/text_oarchive.hpp>
    #include <boost/archive/text_iarchive.hpp>
    #include <boost/serialization/base_object.hpp>
    #include <boost/serialization/export.hpp>
    #include "b.h"
    
    BOOST_CLASS_EXPORT(A)
    BOOST_CLASS_EXPORT(B)
    
    namespace privates {
    
        template <typename Key, typename Key::type PointerToMember> struct store {
            friend typename Key::type get(Key) { return PointerToMember; }
        };
    
        struct elemA {
            typedef int A::*type;
            friend type get(elemA); // ADL-enable
        };
    
        struct elemB {
            typedef int B::*type;
            friend type get(elemB); // ADL-enable
        };
    
        template struct store<elemA, &A::elemA>;
        template struct store<elemB, &B::elemB>;
    
    } // namespace privates
    
    auto& getElemA(A& instance) { return instance.*(get(privates::elemA())); }
    auto& getElemB(B& instance) { return instance.*(get(privates::elemB())); }
    
    namespace boost {
        namespace serialization {
            template<class Archive>
            void serialize(Archive & ar, A& v, unsigned) { ar & getElemA(v); }
    
            template<class Archive>
            void serialize(Archive & ar, B& v, unsigned) { ar & base_object<A>(v) & getElemB(v); }
        }
    }
    
    template <typename T> void run_tests() {
        std::stringstream ss;
        {
            A *obj= new T(747);
            boost::archive::text_oarchive oa(ss);
            oa << obj;
            delete obj;
        }
    
        std::cout << ss.str() << "\n";
    
        {
            A *obj = nullptr;
            boost::archive::text_iarchive ia(ss);
            ia >> obj;
            delete obj;
        }
    }
    
    int main()
    {
        run_tests<A>();
        run_tests<B>();
    }
    

Note it simplifies a few things and at least removed memory-leaks when there were no exceptions.

Output Live On WandBox

22 serialization::archive 15 0 1 0
0 747

22 serialization::archive 15 1 1 B 1 0
0 1 0
1 42 747
sehe
  • 374,641
  • 47
  • 450
  • 633
  • you are using default constructor parameters, which does not show the full picture – nutella_eater Apr 06 '23 at 17:56
  • @nutella_eater Why would I not? That _was_ the question. If you mean that non-default constructors require more work sure. That's both [documented](https://www.boost.org/doc/libs/1_81_0/libs/serialization/doc/serialization.html#constructors) and I've personally written [many answers using it](https://stackoverflow.com/search?tab=newest&q=user%3a85371%20save_construct_data&searchOn=3). Don't hate on an existing answer because you didn't find something else. You're looking in the wrong place. – sehe Apr 06 '23 at 22:42
0

Now I got it: Non-intrusive serialization (text format) with a pimpl style struct as described paragraph 3 here let most members private and reduced the overhead of get/set methods. xml is still open - got compiler errors C2664 and C2789 on Visual Studio 2015. Also json could be interesting ...

Robert
  • 97
  • 6