Search code examples
c++inheritancepolymorphismcereal

Virtual inheritance and polymorphism: Is the cereal library messing with object layout?


I have four classes (A,B,C and D) following the classic diamond pattern and a Container class containing a unique_ptr<A>. I want to serialize these classes using the cereal serialization library.

struct A {int f1; int f2; int f3}

struct B : public virtual A {
    template<typename Archive>
    inline void save(Archive& ar) const {
        std::cerr << "Saving Obj: " << this << std::endl;
        std::cerr << "This: " << &(this->f1) << " " 
            << &(this->f2) << " " << &(this->f3) << std::endl;
        std::cerr << "This: " << this->f1 << " " 
            << this->f2 << " " << this->f3 << std::endl;
    };
}
};

struct C : public virtual A {};

struct D : public B, public C {};

#include <cereal/archives/binary.hpp>
CEREAL_REGISTER_TYPE(B);
CEREAL_REGISTER_TYPE(C);
CEREAL_REGISTER_TYPE(D);

struct Container {
    std::unique_ptr<A> obj;

    template<typename Archive>
    inline void save(Archive& ar) const {
        std::cerr << "Saving Container" << std::endl;
        std::cerr << "Obj Addr: " << obj.get() << std::endl;
        std::cerr << "Obj: " << &(obj->f1) << " " << &(obj->f2) 
            << " " << &(pq->f3) << std::endl;
        std::cerr << "Obj: " << " " << pq->sq_count << " " << pq->sq_bits 
            << " " << pq->dim << std::endl;
        ar(obj); // Call serialization for obj, ie B.save(...)
    }
}

All classes have cereal save and load functions, but I only included them for B and Container, as they are the only ones used in this example.

I use these classes as follows :

std::unique_ptr<A> obj(new B);
obj->f1 = 8;
obj->f2 = 8;
obj->f3 = 128;
std::unique_ptr<Container> db(new Container);
db.obj = std::move(obj);

std::ofstream out_file(out_filename);
cereal::BinaryOutputArchive out_archive(out_file);
out_archive(db);

And I get the following output:

Saving Container
Obj Addr: 0x23d2128 
Obj: 0x23d2130 0x23d2134 0x23d2138 // Fields adresses (f1,f2,f3)
Obj:  8 8 128 // Fields values
Saving Obj: 0x23d2128 // Same object
This: 0x23d2118 0x23d211c 0x23d2120 // Different field adresses !
This: 4293296 0 37569440 // Garbage

My question is: Is it likely that this is a bug in cereal, or is there something that I don't get with virtual inheritance ?

Is it expected that the addresses of the fields of a given object ever change in a C++ program ?


Solution

  • I can't reproduce your error on the current develop branch of cereal, however I can reproduce it on the current master (1.1.2). I modified your code to actually compile:

    #include <cereal/types/memory.hpp>
    #include <cereal/types/polymorphic.hpp>
    #include <cereal/archives/json.hpp>
    #include <fstream>
    #include <iostream>
    
    struct A {
      int f1; int f2; int f3;
      virtual ~A() {}
    
      template<typename Archive>
        void serialize( Archive & ar )
        {
          std::cerr << "Saving A Obj: " << this << std::endl;
          std::cerr << "This: " << &(this->f1) << " "
            << &(this->f2) << " " << &(this->f3) << std::endl;
          std::cerr << "This: " << this->f1 << " "
            << this->f2 << " " << this->f3 << std::endl;
        };
    };
    
    struct B : public virtual A {
      template <class Archive>
      void serialize( Archive & ar )
      {
        std::cerr << "Saving B Obj: " << this << std::endl;
        std::cerr << "This: " << &(this->f1) << " "
          << &(this->f2) << " " << &(this->f3) << std::endl;
        std::cerr << "This: " << this->f1 << " "
          << this->f2 << " " << this->f3 << std::endl;
    
        ar( cereal::virtual_base_class<A>( this ) );
      }
    
      virtual ~B() {}
    };
    
    CEREAL_REGISTER_TYPE(B);
    
    struct Container {
        std::unique_ptr<A> obj;
    
        template<typename Archive>
          void serialize( Archive & ar )
        {
            std::cerr << "Saving Container (A)" << std::endl;
            std::cerr << "Obj Addr: " << obj.get() << std::endl;
            std::cerr << "Obj: " << &(obj->f1) << " " << &(obj->f2)
                << " " << &(obj->f3) << std::endl;
    
            ar(obj); // Call serialization for obj, ie B.save(...)
        }
    };
    
    int main()
    {
      std::unique_ptr<A> ptr(new B());
      ptr->f1 = 8;
      ptr->f2 = 8;
      ptr->f3 = 128;
      std::unique_ptr<Container> db(new Container());
      db->obj = std::move(ptr);
    
      std::stringstream ss;
      {
        cereal::JSONOutputArchive out_archive(ss);
        out_archive(db);
      }
    
      std::cout << ss.str() << std::endl;
    }
    

    The output with 1.1.2:

    Saving Container (A)
    Obj Addr: 0x1738d78
    Obj: 0x1738d80 0x1738d84 0x1738d88
    Saving B Obj: 0x1738d78
    This: 0x1738d78 0x1738d7c 0x1738d80
    This: 4316664 0 8
    Saving A Obj: 0x1738d70
    This: 0x1738d78 0x1738d7c 0x1738d80
    This: 4316664 0 8
    {
        "value0": {
            "ptr_wrapper": {
                "valid": 1,
                "data": {
                    "value0": {
                        "polymorphic_id": 2147483649,
                        "polymorphic_name": "B",
                        "ptr_wrapper": {
                            "valid": 1,
                            "data": {
                                "value0": {}
                            }
                        }
                    }
                }
            }
        }
    }
    

    The output using develop:

    Saving Container (A)
    Obj Addr: 0x1f74e18
    Obj: 0x1f74e20 0x1f74e24 0x1f74e28
    Saving B Obj: 0x1f74e10
    This: 0x1f74e20 0x1f74e24 0x1f74e28
    This: 8 8 128
    Saving A Obj: 0x1f74e18
    This: 0x1f74e20 0x1f74e24 0x1f74e28
    This: 8 8 128
    {
        "value0": {
            "ptr_wrapper": {
                "valid": 1,
                "data": {
                    "value0": {
                        "polymorphic_id": 2147483649,
                        "polymorphic_name": "B",
                        "ptr_wrapper": {
                            "valid": 1,
                            "data": {
                                "value0": {}
                            }
                        }
                    }
                }
            }
        }
    }
    

    So whatever was causing this problem is likely fixed in the current develop branch of cereal, which will be released as 1.2 in the near future.