google / libnop

libnop: C++ Native Object Protocols
Other
575 stars 59 forks source link

Inheritance suppport #21

Closed Astrarog closed 4 years ago

Astrarog commented 4 years ago

Hello! Have you support user-defined structures with inheritance? I have the following data structures where only Base class stores data and Derived classes are only interfaces around the Base class.

class Base
{
protected:
     int a; //example of field
public:
     virtual void foo();  //interface to override

     NOP_STRUCTURE(Base, a);
};

class Derived : public Base
{
     /*no data fields at all*/
public:
     virtual void foo() override;  // new interface
};

So, how can I serialize an instance of Derived class?

eieio commented 4 years ago

User-defined serializable types must be default constructible. Otherwise, behavior-only public inheritance like this should work as expected. The encoder templates should instantiate for Derived the same way they do for Base.

Are you getting an error when you pass instances of Derived to the serializer?

Astrarog commented 4 years ago

Thank you for the response! The structure above works perfectly without any errors. I suppose it was my misunderstanding of how NOP_STRUCTURE macro works. As I understand, if the Derived is inheritant form the Base class with NOP_STRUCTURE provided then Derived class can be serialized as well.

I've done some research and found out that in the example below Derived class serizalizes not only the foo and bar fields from Base class but also its own a and b fields

struct Base
{
     int foo;
     int bar;
     NOP_STRUCTURE(Base, foo, bar);
};

struct Derived : public Base
{
     int a;
     int b;
};

Moreover it is esaly possible to serialize and deserialize one into another:

using Writer = nop::StreamWriter<std::stringstream>;
using Reader = nop::StreamReader<std::stringstream>;

Derived derivedObject;
Base baseObject;

nop::Serializer<Writer> serializer;
serializer.Write(someObject);

nop::Deserializer<Reader> deserializer{serializer.writer().take()};
deserializer.Read(&otherObject);

// baseObject will take the data from derivedObject

I think this all of this is worth mentioning in Getting startred page

eieio commented 4 years ago

Hi Astrarog,

I may be misunderstanding your statements, but I think your comment about the fields in Derived being serialized is incorrect. In the example you provided the serializer/deserializer will not touch a or b at all. Instead, the serializer/deserializer treats both Base and Derived as equivalent because they have the same field description provided by NOP_STRUCTURE.

The way NOP_STRUCTURE(Type, Fields...) works is by constructing a description of the fields using the type system to capture the pointer-to-member of each field. This description is stored in the type alias NOP__MEMBERS within the scope of the struct/class: https://github.com/google/libnop/blob/master/include/nop/structure.h#L62

When Base is passed to the serializer, the encoder templates see that Base::NOP__MEMBERS is defined and use it to build an encoder for the number, type, and order of the fields. When Derived is passed to the deserializer it sees the field description inherited from Base (i.e. Base::NOP_MEMBERS and Derived::NOP_MEMBERS are the same) and builds a decoder for the same set of fields. It does not, however, know anything about Derived::a or Derived::b and leaves them alone entirely.

So you are right that you can interchange Base and Derived when serializing/deserializing. However, the additional fields introduced in Derived don't participate at all.

I hope this clears things up!

Astrarog commented 4 years ago

Hello!

You are right. It was just my wrong obersvation while using libnop.

But I have a question then. So, what should I do to have the ability serialize/deserialize Derived with Derived::a and Derived::b? Is it enough to add NOP_STRUCTURE(Derived, a, b) inside the Derived definition or this macro will override the Derived::NOP_MEMBERS that was inherited from the Base::NOP_MEMBERS? In this case, what should be done to append NOP_MEMBERS to existing Derived::NOP_MEMBERS?

eieio commented 4 years ago

The library isn't designed to support data modeling using inheritance. You could make the members of Base protected and then include them in the member list in Derived: NOP_STRUCTURE(Derived, a, b, foo, bar). However, if you do this you won't be able to use Base and Derived interchangeably when serializing/deserializing.

Instead, you can use composition to build up more complex types. For example:

struct Point3d {
  float x;
  float y;
  float z;
  NOP_STRUCTURE(Point3d, x, y, z);
};

struct Triangle3d {
  Point3d vertices[3];
  NOP_STRUCTURE(Triangle3d, vertices);
};