// // 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; //use bitsery namespace for convenience using namespace bitsery; //some helper types using Buffer = std::vector; using Writer = OutputBufferAdapter; using Reader = 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 = Serializer; using MyDeserializer = 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() == 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; }