// // Created by fraillt on 18.4.26. // #include #include #include #include #include #include #include #include // in order to work with polymorphic types, we need to describe few steps: // 1) describe relationships between base and derived types // this will allow to know what are possible types reachable from base class // 2) bind serializer to base class // this will allow to iterate through all types, and add serialization // functions, without this step compiler would simply remove functions that are // not bound at compile-time even it we use type at runtime. using bitsery::ext::BaseClass; using bitsery::ext::PointerObserver; using bitsery::ext::StdSmartPtr; // define our data structures struct Color { float r{}, g{}, b{}; bool operator==(const Color& o) const { return std::tie(r, g, b) == std::tie(o.r, o.g, o.b); } }; struct Shape { Color clr{}; virtual ~Shape() = 0; }; Shape::~Shape() = default; struct Circle : Shape { int32_t radius{}; bool operator==(const Circle& o) const { return std::tie(radius, clr) == std::tie(o.radius, o.clr); } }; struct Rectangle : Shape { int32_t width{}; int32_t height{}; bool operator==(const Rectangle& o) const { return std::tie(width, height, clr) == std::tie(o.width, o.height, o.clr); } }; struct RoundedRectangle : Rectangle { int32_t radius{}; bool operator==(const RoundedRectangle& o) const { return std::tie(radius, static_cast(*this)) == std::tie(o.radius, static_cast(o)); } }; // define serialization functions template void serialize(S& s, Color& o) { // in real world scenario, it might be possible to serialize this using // ValueRange, to map values in smaller space but for the sake of this example // keep it simple s.value4b(o.r); s.value4b(o.g); s.value4b(o.b); } template void serialize(S& s, Shape& o) { s.object(o.clr); } template void serialize(S& s, Circle& o) { s.ext(o, bitsery::ext::BaseClass{}); s.value4b(o.radius); } template void serialize(S& s, Rectangle& o) { s.ext(o, bitsery::ext::BaseClass{}); s.value4b(o.width); s.value4b(o.height); } template void serialize(S& s, RoundedRectangle& o) { s.ext(o, bitsery::ext::BaseClass{}); s.value4b(o.radius); } // define our test structure struct SomeShapes { std::vector> sharedList; std::unique_ptr uniquePtr; // weak ptr and refPtr will point to sharedList std::weak_ptr weakPtr; Shape* refPtr; }; // creates object, and populates some data SomeShapes createData() { SomeShapes data{}; { auto tmp = new RoundedRectangle{}; tmp->height = 151572; tmp->width = 488795; tmp->radius = 898; tmp->clr.r = 0.5f; tmp->clr.g = 1.0f; tmp->clr.b = 1.0f; data.uniquePtr.reset(tmp); } { auto tmp = new Circle{}; tmp->radius = 75987; tmp->clr.r = 0.5f; tmp->clr.g = 0.0f; tmp->clr.b = 1.0f; data.sharedList.emplace_back(tmp); } { auto tmp = new Rectangle{}; tmp->height = 15157; tmp->width = 48879; tmp->clr.r = 1.0f; tmp->clr.g = 0.0f; tmp->clr.b = 0.0f; data.sharedList.emplace_back(tmp); } data.weakPtr = data.sharedList[0]; data.refPtr = data.sharedList[1].get(); return data; } template void serialize(S& s, SomeShapes& o) { s.ext(o.uniquePtr, StdSmartPtr{}); // to make things more interesting first serialize weakPtr and refPtr, // even though objects that weakPtr and refPtr is serialized later, // bitsery will work regardless s.ext(o.weakPtr, StdSmartPtr{}); s.ext(o.refPtr, PointerObserver{}); s.container(o.sharedList, 100, [](S& s, std::shared_ptr& item) { s.ext(item, StdSmartPtr{}); }); } // STEP 1 // define relationships between base and derived classes namespace bitsery { namespace ext { // for each base class define DIRECTLY derived classes // e.g. PolymorphicBaseClass : PolymorphicDerivedClasses // is incorrect, because RoundedRectangle does not directly derive from Shape template<> struct PolymorphicBaseClass : PolymorphicDerivedClasses { }; template<> struct PolymorphicBaseClass : PolymorphicDerivedClasses { }; } } // convenient type that stores all our types, so that we could easily register // and also it automatically ensures, that classes is registered in the same // order for serialization and deserialization using MyPolymorphicClassesForRegistering = bitsery::ext::PolymorphicClassesList; // some helper types using Buffer = std::vector; using Writer = bitsery::OutputBufferAdapter; using Reader = bitsery::InputBufferAdapter; // we need to define few things in order to work with polymorphism // 1) we need pointer linking context to work with pointers // 2) we need polymorphic context to be able to work with polymorphic types using TContext = std::tuple>; // NOTE: // RTTI can be customizable, if you can't use dynamic_cast and typeid, and have // 'custom' solution using MySerializer = bitsery::Serializer; using MyDeserializer = bitsery::Deserializer; // checks if deserialized data is equal void assertSameShapes(const SomeShapes& data, const SomeShapes& res) { { auto d = dynamic_cast(data.uniquePtr.get()); auto r = dynamic_cast(res.uniquePtr.get()); assert(r != nullptr); assert(*d == *r); } { auto d = dynamic_cast(data.sharedList[0].get()); auto r = dynamic_cast(res.sharedList[0].get()); assert(r != nullptr); assert(*d == *r); } { auto d = dynamic_cast(data.sharedList[1].get()); auto r = dynamic_cast(res.sharedList[1].get()); assert(r != nullptr); assert(*d == *r); } assert(res.weakPtr.lock().get() == res.sharedList[0].get()); assert(res.refPtr == res.sharedList[1].get()); } int main() { auto data = createData(); // create buffer to store data Buffer buffer{}; size_t writtenSize{}; // we will not use quickSerialization/Deserialization functions to show, that // we need to register polymorphic classes, explicitly { // STEP 2 // before start serialization/deserialization, // bind it with base polymorphic types, it will go through all reachable // classes that is defined in first step. NOTE: you dont need to add // Rectangle to reach for RoundedRectangle TContext ctx{}; std::get<1>(ctx).registerBasesList( MyPolymorphicClassesForRegistering{}); // create writer and serialize MySerializer ser{ ctx, buffer }; ser.object(data); ser.adapter().flush(); writtenSize = ser.adapter().writtenBytesCount(); // make sure that pointer linking context is valid // this ensures that all non-owning pointers points to data that has been // serialized, so we can successfully reconstruct pointers after // deserialization assert(std::get<0>(ctx).isValid()); } SomeShapes res{}; { TContext ctx{}; std::get<1>(ctx).registerBasesList( MyPolymorphicClassesForRegistering{}); // deserialize our data MyDeserializer des{ ctx, buffer.begin(), writtenSize }; des.object(res); assert(des.adapter().error() == bitsery::ReaderError::NoError && des.adapter().isCompletedSuccessfully()); // also check for dangling pointers, after deserialization assert(std::get<0>(ctx).isValid()); // clear shared state from pointer linking context, // it is only required if there are any pointers that manage shared state, // e.g. std::shared_ptr assert(res.weakPtr.use_count() == 2); // one in sharedList and one in pointer linking context std::get<0>(ctx).clearSharedState(); assert(res.weakPtr.use_count() == 1); } assertSameShapes(data, res); return 0; }