Magic Clouds
Magic Clouds

Reputation: 13

Boost serialization, loading an archived class by base type gives wrong data

I wrote an example program to figure out Boost's serialization library before I implemented it into a project, however, I got some unexplained behavior.


In my example, I had two classes: a generic BaseClass and a specialized DerivedClass (analogous to what I plan to use Boost for). BaseClass only has one member, a string called name, which defaults to "BaseClass". DerivedClass publicly inherits BaseClass, sets name to something else and has its own unique member, data.

In the main program, I create a DerivedClass with data set to "special cool stuff", and a BaseClass with name "regular stuff". I write both of these to a file with boost::archive::text_oarchive, and read the first object, the DerivedClass, back twice (recreating the std::ifstream both times). The first time reading it back, I put it to a BaseClass*. Calling BaseClass::printData() (a virtual method that prints the std::typeid and name) prints something along the lines of:

 --- Storage done, now loading the first object as BaseClass --- 
9BaseClass: 0

Next, when I load it as a DerivedClass* and call DerivedClass::printData() (which is overridden from BaseClass to include the member data in the output) correctly prints:

 --- Storage done, now loading the first object as DerivedClass --- 
12DerivedClass: DerivedClass AND special cool stuff

Looking in the file I'm writing to, I see this:

22 serialization::archive 15 0 1 0
0 1 0
1 12 DerivedClass 18 special cool stuff 1
2 13 regular stuff

And when I call BaseClass::printData() on the original, pre-serializing DerivedClass, I get this:

9BaseClass: DerivedClass

Obviously, the DerivedClass is being stored correctly. Something about loading it as a BaseClass to check name is messing up. I cannot think of why it would give me a std::string containing 0.

I'm just starting to learn how to use this library, and most of the similar questions and documentation I've found online have no effect (ie, using BOOST_EXPORT_CLASS or BOOST_CLASS_TYPE_INFO, although it could very well be I was using them incorrectly).


Here is my code:

main.cpp

#include <iostream>
#include <fstream>
#include <string>

#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/serialization/nvp.hpp>

#include "baseclass.h"
#include "derivedclass.h"

int main() {
    BaseClass*  testBase = new BaseClass("regular stuff");
    DerivedClass* testDerivate = new DerivedClass("special cool stuff");

    testDerivate->BaseClass::printData();

    std::cout << std::endl << " --- " << "Storing objects in the file 'output'..." << " --- " << std::endl;

    std::ofstream output("storage");

    {
        boost::archive::text_oarchive boost_out(output);
        boost_out << (testDerivate);
        testDerivate->printData();
        boost_out << (testBase);
        testBase->printData();
    }

    std::cout << std::endl << " --- " << "Storage done, now loading the first object as BaseClass" << " --- " << std::endl;

    {
        std::ifstream input("storage");
        BaseClass*  base;
        boost::archive::text_iarchive boost_in(input);
        boost_in >> (base);
        base->printData();
        input.close();
    }

    std::cout << std::endl << " --- " << "Storage done, now loading the first object as DerivedClass" << " --- " << std::endl;

    {
        std::ifstream input("storage");
        DerivedClass* derive;
        boost::archive::text_iarchive boost_in(input);
        boost_in >> (derive);
        derive->printData();
        input.close();
    }

    return 0;
}

baseclass.h

#pragma once

#include <string>
#include <iostream>
#include <typeinfo>

#include <boost/serialization/access.hpp>
#include <boost/serialization/nvp.hpp>

class BaseClass
{
public:
    BaseClass() {
        name = "BaseClass";
    }

    BaseClass(std::string custom) {
        name = custom;
    }

    virtual ~BaseClass() {}

    virtual void printData() {
        std::cout << typeid(*this).name() << ": " << name << std::endl;   
    }

protected:
    std::string name;

private:    
    friend class boost::serialization::access;

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

derivedclass.hpp

#pragma once

#include <string>
#include <iostream>
#include <typeinfo>

#include <boost/serialization/base_object.hpp>
#include <boost/serialization/access.hpp>

#include "baseclass.h"

class DerivedClass :  public BaseClass
{
public:
    DerivedClass() : BaseClass("DerivedClass") {}
    DerivedClass(std::string custom) : BaseClass("DerivedClass") {
        data = custom;
    }

    virtual ~DerivedClass() {}

    void printData() override {
        std::cout << typeid(*this).name() << ": " << name << " AND " << data << std::endl;
    }

protected:
    std::string data;

private:
    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive & ar, const unsigned int version) {
        ar & (boost::serialization::base_object<BaseClass>(*this));
        ar & (data);
    }
};

Sorry if this is a bit long, I wanted to be as descriptive as possible. I'm very new to using Boost and I'm not all that experienced with C++, so even if you just have some general comments on my code, I'd appreciate it.

Upvotes: 1

Views: 458

Answers (1)

sehe
sehe

Reputation: 393674

You're not loading the same types as you're serializing.

So, while can say:

Base* b = new Derived();
boost_out << b;

And deserialize with:

Base* b = nullptr;
boost_in >> b;

You cannot serialize a Derived* and deserialize it as Base* or vice versa.

So, if you know the receiving code must support all derived classes, make it explicit and serialize a Base*.

EXPORT the Types

To let the de-serializing end know what the possible set of derived types is that could be encountered in deserializing a polymorphic base pointer, export the types.

  • https://www.boost.org/doc/libs/1_67_0/libs/serialization/doc/traits.html#export
  • also see https://www.boost.org/doc/libs/1_67_0/libs/serialization/doc/special.html#export:

    Including BOOST_CLASS_EXPORT in the "a.hpp" header itself as one would do with other serialization traits will make it difficult or impossible to follow the rule above regarding inclusion of archive headers before BOOST_CLASS_EXPORT is invoked. This can best be addressed by using BOOST_CLASS_EXPORT_KEY in the header declarations and BOOST_CLASS_EXPORT_IMPLEMENT in the class definition file.

    [ ... snip ... ]

    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 [sic] Archives.

DEMO

See it Live On Wandbox

  • main.cpp

    #include <iostream>
    #include <fstream>
    #include <string>
    
    #include <boost/archive/text_iarchive.hpp>
    #include <boost/archive/text_oarchive.hpp>
    #include <boost/serialization/nvp.hpp>
    //#include <boost/serialization/export.hpp>
    
    #include "baseclass.h"
    #include "derivedclass.h"
    
    int main() {
        BaseClass* testBase = new BaseClass("regular stuff");
        BaseClass* testDerived = new DerivedClass("special cool stuff");
    
        std::cout << std::endl << " --- " << "Storing objects in the file 'output'..." << " --- " << std::endl;
    
        {
            std::ofstream output("storage");
            boost::archive::text_oarchive boost_out(output);
            boost_out << testBase << testDerived;
        }
    
        std::cout << std::endl << " --- " << "Storage done, now loading the first object as BaseClass" << " --- " << std::endl;
    
        {
            std::ifstream input("storage");
            BaseClass* b1;
            BaseClass* b2;
            boost::archive::text_iarchive boost_in(input);
            boost_in >> b1 >> b2;
    
            std::cout << "b1: "; b1->printData();
            std::cout << "b2: "; b2->printData();
        }
    }
    
  • baseclass.h

    #pragma once
    
    #include <string>
    #include <iostream>
    #include <typeinfo>
    
    #include <boost/serialization/access.hpp>
    #include <boost/serialization/nvp.hpp>
    #include <boost/serialization/export.hpp>
    
    class BaseClass
    {
    public:
        BaseClass() {
            name = "BaseClass";
        }
    
        BaseClass(std::string custom) {
            name = custom;
        }
    
        virtual ~BaseClass() {}
    
        virtual void printData() {
            std::cout << typeid(*this).name() << ": " << name << std::endl;   
        }
    
    protected:
        std::string name;
    
    private:    
        friend class boost::serialization::access;
    
        template<class Archive>
        void serialize(Archive & ar, unsigned) {
            ar & (name);
        }
    };
    
    BOOST_CLASS_EXPORT_KEY2(BaseClass, "BaseClass");
    
  • baseclass.cpp

    #include "baseclass.h"
    #include <boost/archive/text_oarchive.hpp>
    #include <boost/archive/text_iarchive.hpp>
    BOOST_CLASS_EXPORT_IMPLEMENT(BaseClass)
    
  • derivedclass.h

    #pragma once
    
    #include <string>
    #include <iostream>
    #include <typeinfo>
    
    #include <boost/serialization/base_object.hpp>
    #include <boost/serialization/access.hpp>
    #include <boost/serialization/export.hpp>
    
    #include "baseclass.h"
    
    class DerivedClass :  public BaseClass
    {
    public:
        DerivedClass() : BaseClass("DerivedClass") {}
        DerivedClass(std::string custom) : BaseClass("DerivedClass") {
            data = custom;
        }
    
        virtual ~DerivedClass() {}
    
        void printData() override {
            std::cout << typeid(*this).name() << ": " << name << " AND " << data << std::endl;
        }
    
    protected:
        std::string data;
    
    private:
        friend class boost::serialization::access;
    
        template<class Archive>
        void serialize(Archive & ar, unsigned) {
            ar & (boost::serialization::base_object<BaseClass>(*this));
            ar & (data);
        }
    };
    
    BOOST_CLASS_EXPORT_KEY2(DerivedClass, "DerivedClass");
    
  • derivedclass.cpp

    #include "derivedclass.h"
    #include <boost/archive/text_oarchive.hpp>
    #include <boost/archive/text_iarchive.hpp>
    BOOST_CLASS_EXPORT_IMPLEMENT(DerivedClass)
    

Output:

Upvotes: 1

Related Questions