Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Why is "inheritance" not supported by FlatBuffers? #4006

Closed
exhau opened this issue Aug 26, 2016 · 13 comments
Closed

Why is "inheritance" not supported by FlatBuffers? #4006

exhau opened this issue Aug 26, 2016 · 13 comments
Labels

Comments

@exhau
Copy link

exhau commented Aug 26, 2016

"Inheritance" I mean here is simply (no multiple inheritance)

table Monster { .. }
table XxxMonster : Monster {
NewMember1 : type
NewMember2 : type
}

The implementation could be

struct Monster : private flatbuffers::Table {
    VT_POS = 4,
    VT_MANA = 6,
    VT_HP = 8,
    VT_NAME = 10,
    VT_INVENTORY = 14,
    VT_COLOR = 16,
    VT_WEAPONS = 18,
    VT_EQUIPPED_TYPE = 20,
    VT_EQUIPPED = 22
...
}

struct XxxMonster : public Monster {
    VT_NewMember1 = 26
    VT_NewMember2 = 28
...
}

I don't see any reason why there is no such a feature. Is there any concern?

Thanks

@ghost
Copy link

ghost commented Aug 26, 2016

The big reason is that the fragile base class problem would be even more problematic here than in other systems. In FlatBuffers you can only add new fields at the end (or as the next higher id), which means that once you inherit, you can never add anything safely to the base class anymore.

@exhau
Copy link
Author

exhau commented Aug 27, 2016

I think it can be a feature. One can choose to use or not to use. In my case, I plan to use flatbuffers internally, like in-memory data storage (can be temporarily stored on hard drive).

In current design without inheritance, what is the best practice to represent this relationship?

@JMLX42
Copy link

JMLX42 commented Aug 27, 2016

Composition ?

table Monster { .. }
table XxxMonster {
  base:Monster;
  ...
}

@exhau
Copy link
Author

exhau commented Aug 28, 2016

Thank you for your reply. This is an option, but disadvantage is polymorphism.

Let say there is a buffer from somewhere. I know it is a kind of Monster but don't know the exact type(XxxMonster).
Using the inheritance way, the buffer can be read as either a Monster or a XxxMonster with no problem.
If we don't know the number of inheritance layers, there is no way to read a buffer of derived type as a base type unless inserting type information into buffer.

@JMLX42
Copy link

JMLX42 commented Aug 28, 2016

table Monster { .. }
table XxxMonster {
  base:Monster;
  ...
}
table YyyMonster {
  base:Monster;
  ...
}
union AnyMonster {
  XxxMonster,
  YyyMonster
}

You can use AnyMonster to store an XxxMonster or an YyyMonster.
FlatBuffers will also store the actual type of the union field so you can cast it to the actual type (cf "Union" in https://google.github.io/flatbuffers/flatbuffers_guide_writing_schema.html).

@xyczero
Copy link

xyczero commented Sep 9, 2016

@promethe42 This solution may be the best. Otherwise , we have to generate code for your inheritance by manually when deserializing from the hard drive.

@exhau
Copy link
Author

exhau commented Sep 22, 2016

Thank you guys.

What I am planning to do is to wrap a buffer as a normal class. When a buffer is read from disk or in-memory storage and binded to a certain type, getters can directly access the buf without copying anything. If we wish to edit it, we can unpack it to an object type(copy/deserialize), do changes and then save the changes. Saving is actually packing the object to byte array again.

However, on the other hand, I also wish the classes can be used as normal ones, with runtime polymorphism.

Inheritance can be complex and unpredictable, for example, B:A, C:B, D:B,E:D... (fortunately no multiple inheritance like C:B,A). In the example, class A and E may belong to different modules developed by two teams. E's module is a standalone addon. The solution by "Union" is not very reasonable in my case. The module containing A can't know E in advance.

Our application has a lot of data managed by Key-Value database, and there are frequent derializations (which are expensive) right now. In most time, they are immutable.
And we don't plan to use flatbuffer for network communication or persisted storage. All buffers are temporary running data. Compared to the concern of fragile base class, it is more important for us that inheritances from different team do not touch framework. In other words, we stick to OO but wish to change the memory layout of an (readonly)object. In our case, if framework changes, everything re-generates the code.

Sorry if I didn't make it clear. I just want to utilize flatbuffer to avoid unnecessary copy and deserialization within the library. Maybe it's a misuse or overkill. Forgive me for my amateurish.

@ghost
Copy link

ghost commented Sep 23, 2016

I understand your use case, but it is a non-trivial feature (assuming we support it in all languages), and it is something that most users should not be using, since they'll get themselves into trouble. I think @promethe42 's solution is the best, assuming you need to pass part of a "derived class" to a function that expects a "base class".

@exhau
Copy link
Author

exhau commented Sep 23, 2016

@gwvo I understand the situation. Do you think it is doable if I fork the repo and do the change myself?

assuming you need to pass part of a "derived class" to a function that expects a "base class".

I need to pass the entire derived class instead of a part of to a function in base module. And then cast it to a base type. Virtual functions should work.

@ghost
Copy link

ghost commented Sep 23, 2016

You can't have virtual methods in the table accessor objects, since they point to inside the FlatBuffer.

You can still have polymorphism through templates (in C++), i.e. any two object that happen to have the same fields can be accessed this way. You can achieve this relatively simply by just having a schema feature that copies all the fields of a base table.

@exhau
Copy link
Author

exhau commented Sep 23, 2016

Virtual methods are implemented in the wrapper class. As long as the buffer has the type information to construct correct wrapper class.

Assuming there exists "Inheritance" by appending, one can cast a buffer of derived buffer to any base type buffer without any cost.

To make it clear, the design could be like the following code.

BaseBuffer { int typehash; }   // general base with only type information
MonsterBuffer : BaseBuffer{...}
XxxMonsterBuffer : MonsterBuffer{...}

WrapperBase
{
protected:
BaseBuffer* buf
}

WrapperMonster : WrapperBase
{
public:
   getters;
private:
    MonsterBuffer* TypeCast() { return static_cast<MonsterBuffer*>(buf);}
}

WrapperXxxMonster : WrapperMonster
{
public:
   getters;
private:
    XxxMonsterBuffer* TypeCast() { return static_cast<XxxMonsterBuffer*>(buf);}
}

Given a raw buffer, we can retrieve type information from the very beginning, then use correct constructor (here it is WrapperXxxMonster ). Wrapper classes now can have virtual methods

@grandprixgp
Copy link

Are there any full CPP examples of this type of pseudo-inheritance shown by @promethe42 here?

@stale
Copy link

stale bot commented Jul 27, 2019

This issue has been automatically marked as stale because it has not had activity for 1 year. It will be automatically closed if no further activity occurs. To keep it open, simply post a new comment. Maintainers will re-open on new activity. Thank you for your contributions.

@stale stale bot added the stale label Jul 27, 2019
@stale stale bot closed this as completed Aug 22, 2019
chaokunyang added a commit to apache/fury that referenced this issue Mar 30, 2024
…tion (#1413)

## What does this PR do?

This PR standardizes fury cross-language serialization specification. It
comes with following changes:
- Remove type tag from the protocol since it introduce space and
performance overhead to the implementation. The `type tag` version can
be seen in
https://github.com/apache/incubator-fury/blob/6ea2e0b83d5449d63ca62296ff0dfd67b96c5bc5/docs/protocols/xlang_object_graph_spec.md
.
- Fury preserves `0~63` for internal types, but let users register type
by id from `0`(added by 64 automatically) to setup type mapping between
languages.
- Streamline the type systems, only
`bool/byte/i16/i32/i64/half-float/float/double/string/enum/list/set/map/Duration/Timestamp/decimal/binary/array/tensor/sparse/tensor/arrow/record/batch/arrow/table`
are allowed.
- Formulized the binary format for above types.
- Add type disambiguation: the deserialization are determined by data
type in serialized binary and target type jointly.
- Introduce meta string encoding algorithm for field name to reduce
space cost by 3/8.
- Introduce schema consist mode format for struct.
- Introduce schema envolution mode for struct: 
- this mode can embeed meta in the data or share across multiple
messages,
- it can avoid the cost of type tag comparison in frameworks like
protobuf

This protocol also supports object inheriance for xlang serializaiton.
This is a feature request that users has been discussed for a long time
in protobuf/flatbuffer:
- google/flatbuffers#4006
- protocolbuffers/protobuf#5645

Although there are some languages such as `rust/golang` doesn't support
inheriance, there are many cases only langauges like
`java/c#/python/javascript` are involved, and the support for inheriance
is not complexed in the protocol level, so we added the inheriance
support in the protocol. And in languages such as `rust/golang`, we can
use some annotation to mark composition field as parent class for
serialization layout, or we can disable inheriance foor such languages
at the protocol level.
 
The protocol support polymorphic natively by type id, so I don't include
types such as `OneOf/Union`. With this protocol, you can even serialize
multiple rust `dyn trait` object which implement same trait., and get
exactly the same objects when deserialization.

## Related issue
This PR Closes #1418

---------

Co-authored-by: Twice <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants