Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 ?

like image 423
Xion345 Avatar asked Feb 13 '16 01:02

Xion345


1 Answers

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.

like image 160
Azoth Avatar answered Oct 16 '22 10:10

Azoth