تسلسل ثنائي حديث لـ C++20 ومكتبة RPC، مع ملف رأس واحد فقط.
هذه المكتبة هي خليفة لـ zpp::serializer. تحاول المكتبة أن تكون أسهل في الاستخدام، ولكنها تحتوي على واجهة برمجة تطبيقات مشابهة إلى حد ما لسابقتها.
constexpr
zpp::bits
تتم كتابتها بسهولة أكبر من zpp::serializer
.zpp::serializer
مع 8 بايت ثابتة من معرف تسلسل sha1. بالنسبة للعديد من الأنواع، يكون تمكين التسلسل شفافًا ولا يتطلب أي أسطر إضافية من التعليمات البرمجية. يجب أن تكون هذه الأنواع من النوع التجميعي، مع أعضاء غير مصفوفة. فيما يلي مثال لفئة person
بالاسم والعمر:
struct person
{
std::string name;
int age{};
};
مثال على كيفية إجراء تسلسل للشخص من وإلى متجه البايتات:
// The `data_in_out` utility function creates a vector of bytes, the input and output archives
// and returns them so we can decompose them easily in one line using structured binding like so:
auto [data, in, out] = zpp::bits::data_in_out();
// Serialize a few people:
out (person{ " Person1 " , 25 }, person{ " Person2 " , 35 });
// Define our people.
person p1, p2;
// We can now deserialize them either one by one `in(p1)` `in(p2)`, or together, here
// we chose to do it together in one line:
in (p1, p2);
يكاد يكون هذا المثال ناجحًا، حيث يتم تحذيرنا من أننا نتخلص من القيمة المرجعة. للتحقق من الخطأ، تابع القراءة.
نحن بحاجة إلى التحقق من وجود أخطاء، وتقدم المكتبة طرقًا متعددة للقيام بذلك - على أساس القيمة المرجعة، أو على أساس الاستثناء، أو على أساس zpp::throwing.
الطريقة المعتمدة على قيمة الإرجاع هي الأكثر وضوحًا، أو إذا كنت تفضل القيم المرجعة فقط:
auto [data, in, out] = zpp::bits::data_in_out();
auto result = out(person{ " Person1 " , 25 }, person{ " Person2 " , 35 });
if (failure(result)) {
// `result` is implicitly convertible to `std::errc`.
// handle the error or return/throw exception.
}
person p1, p2;
result = in(p1, p2);
if (failure(result)) {
// `result` is implicitly convertible to `std::errc`.
// handle the error or return/throw exception.
}
الطريقة القائمة على الاستثناءات باستخدام .or_throw()
(اقرأ هذا كـ "succeed or throw" - وبالتالي or_throw()
):
int main ()
{
try {
auto [data, in, out] = zpp::bits::data_in_out ();
// Check error using `or_throw()` which throws an exception.
out (person{ " Person1 " , 25 }, person{ " Person2 " , 35 }). or_throw ();
person p1, p2;
// Check error using `or_throw()` which throws an exception.
in (p1, p2). or_throw ();
return 0 ;
} catch ( const std:: exception & error) {
std::cout << " Failed with error: " << error. what () << ' n ' ;
return 1 ;
} catch (...) {
std::cout << " Unknown error n " ;
return 1 ;
});
}
هناك خيار آخر وهو zpp:: throwing حيث يتحول التحقق من الأخطاء إلى اثنين من عمليات co_await
البسيطة، لفهم كيفية التحقق من الخطأ، نقدم وظيفة رئيسية كاملة:
int main ()
{
return zpp::try_catch ([]() -> zpp::throwing< int > {
auto [data, in, out] = zpp::bits::data_in_out ();
// Check error using `co_await`, which suspends the coroutine.
co_await out (person{ " Person1 " , 25 }, person{ " Person2 " , 35 });
person p1, p2;
// Check error using `co_await`, which suspends the coroutine.
co_await in (p1, p2);
co_return 0 ;
}, [](zpp::error error) {
std::cout << " Failed with error: " << error. message () << ' n ' ;
return 1 ;
}, []( /* catch all */ ) {
std::cout << " Unknown error n " ;
return 1 ;
});
}
تستخدم جميع الطرق المذكورة أعلاه رموز الخطأ التالية داخليًا ويمكن التحقق منها باستخدام عامل المقارنة بناءً على قيمة الإرجاع، أو عن طريق فحص رمز الخطأ الداخلي لـ std::system_error
أو zpp::throwing
اعتمادًا على الكود الذي استخدمته:
std::errc::result_out_of_range
- محاولة الكتابة أو القراءة من مخزن مؤقت قصير جدًا.std::errc::no_buffer_space
- قد يتجاوز المخزن المؤقت المتزايد حدود التخصيص أو يتجاوز السعة.std::errc::value_too_large
- ترميز varint (عدد صحيح متغير الطول) يتجاوز حدود التمثيل.std::errc::message_size
- حجم الرسالة يتجاوز حدود التخصيص التي يحددها المستخدم.std::errc::not_supported
- حاول استدعاء RPC غير المدرج على أنه مدعوم.std::errc::bad_message
- حاول قراءة متغير من النوع غير المعروف.std::errc::invalid_argument
- محاولة إجراء تسلسل لمؤشر فارغ أو متغير بدون قيمة.std::errc::protocol_error
- محاولة إلغاء تسلسل رسالة بروتوكول غير صالحة. بالنسبة لمعظم الأنواع غير المجمعة (أو الأنواع المجمعة مع أعضاء المصفوفة)، فإن تمكين التسلسل هو سطر واحد. فيما يلي مثال لفئة person
غير مجمعة:
struct person
{
// Add this line to your class with the number of members:
using serialize = zpp::bits::members< 2 >; // Two members
person ( auto && ...){ /* ... */ } // Make non-aggregate.
std::string name;
int age{};
};
في معظم الأوقات، يمكن للأنواع التي نقوم بتسلسلها أن تعمل مع الربط المنظم، وتستفيد هذه المكتبة من ذلك، لكنك تحتاج إلى توفير عدد الأعضاء في فصلك حتى يعمل هذا باستخدام الطريقة المذكورة أعلاه.
يعمل هذا أيضًا مع البحث المعتمد على الوسيطة، مما يسمح بعدم تعديل الفئة المصدر:
namespace my_namespace
{
struct person
{
person ( auto && ...){ /* ... */ } // Make non-aggregate.
std::string name;
int age{};
};
// Add this line somewhere before the actual serialization happens.
auto serialize ( const person & person) -> zpp::bits::members<2>;
} // namespace my_namespace
في بعض المترجمين، تعمل SFINAE مع requires expression
ضمن if constexpr
unevaluated lambda expression
. وهذا يعني أنه حتى مع الأنواع غير المجمعة، يمكن اكتشاف عدد الأعضاء تلقائيًا في الحالات التي يكون فيها جميع الأعضاء في نفس البنية. للاشتراك، حدد ZPP_BITS_AUTODETECT_MEMBERS_MODE=1
.
// Members are detected automatically, no additional change needed.
struct person
{
person ( auto && ...){ /* ... */ } // Make non-aggregate.
std::string name;
int age{};
};
يعمل هذا مع clang 13
، ولكن قابلية نقل هذا غير واضحة، لأنه في gcc
لا يعمل (إنه خطأ فادح) وينص صراحة في المعيار على أن هناك نية لعدم السماح بـ SFINAE في حالات مماثلة، لذلك يتم إيقاف تشغيله بشكل افتراضي.
إذا كان أعضاء بياناتك أو المنشئ الافتراضي خاصين، فستحتاج إلى أن تصبح صديقًا لـ zpp::bits::access
كما يلي:
struct private_person
{
// Add this line to your class.
friend zpp::bits::access;
using serialize = zpp::bits::members< 2 >;
private:
std::string name;
int age{};
};
لتمكين حفظ وتحميل أي كائن باستخدام التسلسل الصريح، والذي يعمل بغض النظر عن توافق الارتباط المنظم، أضف الأسطر التالية إلى فصلك:
constexpr static auto serialize ( auto & archive, auto & self)
{
return archive (self. object_1 , self. object_2 , ...);
}
لاحظ أن object_1, object_2, ...
هم أعضاء البيانات غير الثابتة في فصلك.
فيما يلي مثال لفئة شخص مرة أخرى مع وظيفة تسلسل صريحة:
struct person
{
constexpr static auto serialize ( auto & archive, auto & self)
{
return archive (self. name , self. age );
}
std::string name;
int age{};
};
أو باستخدام البحث المعتمد على الوسيطة:
namespace my_namespace
{
struct person
{
std::string name;
int age{};
};
constexpr auto serialize ( auto & archive, person & person)
{
return archive (person. name , person. age );
}
constexpr auto serialize ( auto & archive, const person & person)
{
return archive (person. name , person. age );
}
} // namespace my_namespace
إنشاء أرشيفات الإدخال والإخراج معًا وبشكل منفصل عن البيانات:
// Create both a vector of bytes, input and output archives.
auto [data, in, out] = zpp::bits::data_in_out();
// Create just the input and output archives, and bind them to the
// existing vector of bytes.
std::vector<std::byte> data;
auto [in, out] = zpp::bits::in_out(data);
// Create all of them separately
std::vector<std::byte> data;
zpp::bits::in in (data);
zpp::bits::out out (data);
// When you need just data and in/out
auto [data, in] = zpp::bits::data_in();
auto [data, out] = zpp::bits::data_out();
يمكن إنشاء الأرشيفات من أحد أنواع البايت:
// Either one of these work with the below.
std::vector<std::byte> data;
std::vector< char > data;
std::vector< unsigned char > data;
std::string data;
// Automatically works with either `std::byte`, `char`, `unsigned char`.
zpp::bits::in in (data);
zpp::bits::out out (data);
يمكنك أيضًا استخدام كائنات بيانات ذات حجم ثابت مثل المصفوفة و std::array
وأنواع العرض مثل std::span
المشابهة لما سبق. تحتاج فقط إلى التأكد من وجود حجم كافٍ نظرًا لأنه لا يمكن تغيير حجمها:
// Either one of these work with the below.
std::byte data[ 0x1000 ];
char data[ 0x1000 ];
unsigned char data[ 0x1000 ];
std::array<std::byte, 0x1000 > data;
std::array< char , 0x1000 > data;
std::array< unsigned char , 0x1000 > data;
std::span<std::byte> data = /* ... */ ;
std::span< char > data = /* ... */ ;
std::span< unsigned char > data = /* ... */ ;
// Automatically works with either `std::byte`, `char`, `unsigned char`.
zpp::bits::in in (data);
zpp::bits::out out (data);
عند استخدام متجه أو سلسلة، فإنه ينمو تلقائيًا إلى الحجم الصحيح، ومع ذلك، مع ما ورد أعلاه تقتصر البيانات على حدود المصفوفات أو الامتدادات.
عند إنشاء الأرشيف بأي من الطرق المذكورة أعلاه، من الممكن تمرير عدد متنوع من المعلمات التي تتحكم في سلوك الأرشيف، مثل ترتيب البايت وأنواع الحجم الافتراضي وتحديد سلوك الإلحاق وما إلى ذلك. تمت مناقشة هذا الأمر في بقية ملف README.
كما ذكرنا أعلاه، فإن المكتبة تكاد تكون ذات معنى، فيما يلي مثال على استخدام المصفوفة ككائن بيانات ولكن أيضًا استخدامها في وقت الترجمة لإجراء تسلسل وإلغاء تسلسل مجموعة من الأعداد الصحيحة:
constexpr auto tuple_integers ()
{
std::array<std::byte, 0x1000 > data{};
auto [in, out] = zpp::bits::in_out (data);
out (std::tuple{ 1 , 2 , 3 , 4 , 5 }). or_throw ();
std::tuple t{ 0 , 0 , 0 , 0 , 0 };
in (t). or_throw ();
return t;
}
// Compile time check.
static_assert (tuple_integers() == std::tuple{ 1 , 2 , 3 , 4 , 5 });
للراحة، توفر المكتبة أيضًا بعض وظائف التسلسل المبسطة لوقت الترجمة:
using namespace zpp ::bits::literals ;
// Returns an array
// where the first bytes are those of the hello world string and then
// the 1337 as 4 byte integer.
constexpr std::array data =
zpp::bits::to_bytes< " Hello World! " _s, 1337 >();
static_assert (
zpp::bits::from_bytes<data,
zpp::bits::string_literal< char , 12 >,
int >() == std::tuple{ " Hello World! " _s, 1337 });
استعلم عن موضع in
out
باستخدام position()
، وبعبارة أخرى، قراءة البايتات وكتابتها على التوالي:
std:: size_t bytes_read = in.position();
std:: size_t bytes_written = out.position();
أعد ضبط الوضع للخلف أو للأمام، أو إلى البداية، استخدمه بحذر شديد:
in.reset(); // reset to beginning.
in.reset(position); // reset to position.
in.position() -= sizeof ( int ); // Go back an integer.
in.position() += sizeof ( int ); // Go forward an integer.
out.reset(); // reset to beginning.
out.reset(position); // reset to position.
out.position() -= sizeof ( int ); // Go back an integer.
out.position() += sizeof ( int ); // Go forward an integer.
عند إجراء تسلسل لأنواع المكتبات القياسية ذات الطول المتغير، مثل المتجهات والسلاسل وأنواع العرض مثل عرض النطاق والسلسلة، تقوم المكتبة أولاً بتخزين عدد صحيح مكون من 4 بايت يمثل الحجم، متبوعًا بالعناصر.
std::vector v = { 1 , 2 , 3 , 4 };
out (v);
in (v);
السبب في أن نوع الحجم الافتراضي هو 4 بايت (على سبيل المثال std::uint32_t
) هو إمكانية النقل بين البنيات المختلفة، بالإضافة إلى أن معظم البرامج لا تصل أبدًا إلى حالة تحتوي على أكثر من 2^32 عنصرًا، وقد يكون الأمر كذلك من الظلم دفع ثمن حجم 8 بايت بشكل افتراضي.
بالنسبة لأنواع أحجام محددة ليست 4 بايت، استخدم zpp::bits::sized
/ zpp::bits::sized_t
كما يلي:
// Using `sized` function:
std::vector< int > v = { 1 , 2 , 3 , 4 };
out (zpp::bits::sized<std:: uint16_t >(v));
in (zpp::bits::sized<std:: uint16_t >(v));
// Using `sized_t` type:
zpp::bits:: sized_t <std::vector< int >, std:: uint16_t > v = { 1 , 2 , 3 , 4 };
out (v);
in (v);
تأكد من أن نوع الحجم كبير بما يكفي للكائن المتسلسل، وإلا سيتم تسلسل عناصر أقل، وفقًا لقواعد التحويل للأنواع غير الموقعة.
يمكنك أيضًا اختيار عدم إجراء تسلسل للحجم على الإطلاق، كما يلي:
// Using `unsized` function:
std::vector< int > v = { 1 , 2 , 3 , 4 };
out (zpp::bits::unsized(v));
in (zpp::bits::unsized(v));
// Using `unsized_t` type:
zpp::bits:: unsized_t <std::vector< int >> v = { 1 , 2 , 3 , 4 };
out (v);
in (v);
في الأماكن التي يكون فيها ذلك شائعًا، توجد إعلانات مستعارة للإصدارات ذات الحجم/غير الحجم من الأنواع، على سبيل المثال، هنا vector
و span
، والبعض الآخر مثل string
و string_view
وما إلى ذلك يستخدمون نفس النمط.
zpp::bits::vector1b<T>; // vector with 1 byte size.
zpp::bits::vector2b<T>; // vector with 2 byte size.
zpp::bits::vector4b<T>; // vector with 4 byte size == default std::vector configuration
zpp::bits::vector8b<T>; // vector with 8 byte size.
zpp::bits::static_vector<T>; // unsized vector
zpp::bits::native_vector<T>; // vector with native (size_type) byte size.
zpp::bits::span1b<T>; // span with 1 byte size.
zpp::bits::span2b<T>; // span with 2 byte size.
zpp::bits::span4b<T>; // span with 4 byte size == default std::span configuration
zpp::bits::span8b<T>; // span with 8 byte size.
zpp::bits::static_span<T>; // unsized span
zpp::bits::native_span<T>; // span with native (size_type) byte size.
تسلسل أنواع ذات حجم ثابت مثل المصفوفات، std::array
s، std::tuple
s لا يتضمن أي حمل باستثناء العناصر المتبوعة ببعضها البعض.
من الممكن تغيير نوع الحجم الافتراضي للأرشيف بأكمله أثناء الإنشاء:
zpp::bits::in in (data, zpp::bits::size1b{}); // Use 1 byte for size.
zpp::bits::out out (data, zpp::bits::size1b{}); // Use 1 byte for size.
zpp::bits::in in (data, zpp::bits::size2b{}); // Use 2 bytes for size.
zpp::bits::out out (data, zpp::bits::size2b{}); // Use 2 bytes for size.
zpp::bits::in in (data, zpp::bits::size4b{}); // Use 4 bytes for size.
zpp::bits::out out (data, zpp::bits::size4b{}); // Use 4 bytes for size.
zpp::bits::in in (data, zpp::bits::size8b{}); // Use 8 bytes for size.
zpp::bits::out out (data, zpp::bits::size8b{}); // Use 8 bytes for size.
zpp::bits::in in (data, zpp::bits::size_native{}); // Use std::size_t for size.
zpp::bits::out out (data, zpp::bits::size_native{}); // Use std::size_t for size.
zpp::bits::in in (data, zpp::bits::no_size{}); // Don't use size, for very special cases, since it is very limiting.
zpp::bits::out out (data, zpp::bits::no_size{}); // Don't use size, for very special cases, since it is very limiting.
// Can also do it together, for example for 2 bytes size:
auto [data, in, out] = data_in_out(zpp::bits::size2b{});
auto [data, out] = data_out(zpp::bits::size2b{});
auto [data, in] = data_in(zpp::bits::size2b{});
تعرف معظم الأنواع في المكتبة كيفية تحسين الكائنات وتسلسلها على هيئة بايت. ومع ذلك، يتم تعطيله عند استخدام وظائف التسلسل الصريحة.
إذا كنت تعلم أن النوع الخاص بك قابل للتسلسل تمامًا مثل البايتات الأولية، وكنت تستخدم تسلسلًا صريحًا، فيمكنك الاشتراك وتحسين تسلسله إلى مجرد memcpy
:
struct point
{
int x;
int y;
constexpr static auto serialize ( auto & archive, auto & self)
{
// Serialize as bytes, instead of serializing each
// member separately. The overall result is the same, but this may be
// faster sometimes.
return archive ( zpp::bits::as_bytes (self));
}
};
من الممكن أيضًا القيام بذلك مباشرةً من متجه أو نطاق من الأنواع القابلة للنسخ بشكل تافه، وهذه المرة نستخدم bytes
بدلاً من as_bytes
لأننا نحول محتويات المتجه إلى بايتات بدلاً من كائن المتجه نفسه (البيانات التي يشير إليها المتجه بدلاً من الكائن المتجه):
std::vector<point> points;
out (zpp::bits::bytes(points));
in (zpp::bits::bytes(points));
ومع ذلك، في هذه الحالة لا يتم إجراء تسلسل للحجم، وقد يتم توسيع هذا في المستقبل لدعم تسلسل الحجم المماثل لأنواع العرض الأخرى. إذا كنت بحاجة إلى إجراء تسلسل بالبايت وتريد الحجم، كحل بديل، فمن الممكن الإرسال إلى std::span<std::byte>
.
على الرغم من عدم وجود أداة مثالية للتعامل مع التوافق العكسي للبنيات بسبب عدم الحمل الزائد للتسلسل، يمكنك استخدام std::variant
كطريقة لإصدار فئاتك أو إنشاء إرسال لطيف يعتمد على تعدد الأشكال، وإليك الطريقة:
namespace v1
{
struct person
{
using serialize = zpp::bits::members< 2 >;
auto get_hobby () const
{
return " <none> " sv;
}
std::string name;
int age;
};
} // namespace v1
namespace v2
{
struct person
{
using serialize = zpp::bits::members< 3 >;
auto get_hobby () const
{
return std::string_view (hobby);
}
std::string name;
int age;
std::string hobby;
};
} // namespace v2
ثم إلى التسلسل نفسه:
auto [data, in, out] = zpp::bits::data_in_out();
out (std::variant<v1::person, v2::person>(v1::person{ " Person1 " , 25 }))
.or_throw();
std::variant<v1::person, v2::person> v;
in (v).or_throw();
std::visit ([]( auto && person) {
( void ) person. name == " Person1 " ;
( void ) person. age == 25 ;
( void ) person. get_hobby () == " <none> " ;
}, v);
out (std::variant<v1::person, v2::person>(
v2::person{ " Person2 " , 35 , " Basketball " }))
.or_throw();
in (v).or_throw();
std::visit ([]( auto && person) {
( void ) person. name == " Person2 " ;
( void ) person. age == 35 ;
( void ) person. get_hobby () == " Basketball " ;
}, v);
الطريقة التي يتم بها إجراء تسلسل للمتغير هي إجراء تسلسل للفهرس الخاص به (0 أو 1) باعتباره std::byte
قبل إجراء تسلسل للكائن الفعلي. يعد هذا أمرًا فعالاً للغاية، ولكن في بعض الأحيان قد يرغب المستخدمون في اختيار معرف تسلسل صريح لذلك، راجع النقطة أدناه
لتعيين معرف تسلسل مخصص، تحتاج إلى إضافة سطر إضافي داخل/خارج صفك على التوالي:
using namespace zpp ::bits::literals ;
// Inside the class, this serializes the full string "v1::person" before you serialize
// the person.
using serialize_id = zpp::bits::id< " v1::person " _s>;
// Outside the class, this serializes the full string "v1::person" before you serialize
// the person.
auto serialize_id ( const person &) -> zpp::bits::id<"v1::person"_s>;
لاحظ أن معرفات التسلسل للأنواع في المتغير يجب أن تتطابق في الطول، وإلا سيحدث خطأ في الترجمة.
يمكنك أيضًا استخدام أي تسلسل من البايتات بدلاً من سلسلة قابلة للقراءة، بالإضافة إلى عدد صحيح أو أي نوع حرفي، فيما يلي مثال على كيفية استخدام تجزئة سلسلة كمعرف تسلسل:
using namespace zpp ::bits::literals ;
// Inside:
using serialize_id = zpp::bits::id< " v1::person " _sha1>; // Sha1
using serialize_id = zpp::bits::id< " v1::person " _sha256>; // Sha256
// Outside:
auto serialize_id ( const person &) -> zpp::bits::id<"v1::person"_sha1>; // Sha1
auto serialize_id ( const person &) -> zpp::bits::id<"v1::person"_sha256>; // Sha256
يمكنك أيضًا إجراء تسلسل للبايتات الأولى فقط من التجزئة، كما يلي:
// First 4 bytes of hash:
using serialize_id = zpp::bits::id< " v1::person " _sha256, 4 >;
// First sizeof(int) bytes of hash:
using serialize_id = zpp::bits::id< " v1::person " _sha256_int>;
يتم بعد ذلك تحويل النوع إلى بايت في وقت الترجمة باستخدام (... انتظر) zpp::bits::out
في وقت الترجمة، لذلك طالما أن النوع الحرفي الخاص بك قابل للتسلسل وفقًا لما ورد أعلاه، يمكنك استخدامه كنوع معرف التسلسل يتم إجراء تسلسل للمعرف إلى std::array<std::byte, N>
ولكن بالنسبة إلى 1 و2 و4 و8 بايت، يكون نوعه الأساسي هو std::byte
std::uint16_t
و std::uin32_t
و std::uint64_t
على التوالي لسهولة الاستخدام والكفاءة.
إذا كنت تريد إجراء تسلسل للمتغير بدون معرف، أو إذا كنت تعلم أن المتغير سيكون له معرف معين عند إلغاء التسلسل، فيمكنك القيام بذلك باستخدام zpp::bits::known_id
لتغليف المتغير الخاص بك:
std::variant<v1::person, v2::person> v;
// Id assumed to be v2::person, and is not serialized / deserialized.
out (zpp::bits::known_id< " v2::person " _sha256_int>(v));
in (zpp::bits::known_id< " v2::person " _sha256_int>(v));
// When deserializing you can pass the id as function parameter, to be able
// to use outside of compile time context. `id_v` stands for "id value".
// In our case 4 bytes translates to a plain std::uint32_t, so any dynamic
// integer could fit as the first parameter to `known_id` below.
in (zpp::bits::known_id(zpp::bits::id_v< " v2::person " _sha256_int>, v));
وصف الحروف المساعدة في المكتبة:
using namespace zpp ::bits::literals ;
" hello " _s // Make a string literal.
" hello " _b // Make a binary data literal.
" hello " _sha1 // Make a sha1 binary data literal.
" hello " _sha256 // Make a sha256 binary data literal.
" hello " _sha1_int // Make a sha1 integer from the first hash bytes.
" hello " _sha256_int // Make a sha256 integer from the first hash bytes.
" 01020304 " _decode_hex // Decode a hex string into bytes literal.
zpp::bits::apply
، يجب أن تكون الوظيفة غير قالب ولها حمل زائد واحد بالضبط: int foo (std::string s, int i)
{
// s == "hello"s;
// i == 1337;
return 1338 ;
}
auto [data, in, out] = zpp::bits::data_in_out();
out ( " hello " s, 1337 ).or_throw();
// Call the foo in one of the following ways:
// Exception based:
zpp::bits::apply (foo, in).or_throw() == 1338;
// zpp::throwing based:
co_await zpp::bits::apply (foo, in) == 1338;
// Return value based:
if ( auto result = zpp::bits::apply(foo, in);
failure (result)) {
// Failure...
} else {
result. value () == 1338 ;
}
عندما لا تتلقى وظيفتك أي معلمات، فإن التأثير هو مجرد استدعاء الوظيفة دون إلغاء التسلسل والقيمة المرجعة هي القيمة المرجعة لوظيفتك. عندما ترجع الدالة فارغة، لا توجد قيمة للنوع الناتج.
توفر المكتبة أيضًا واجهة RPC (استدعاء الإجراء البعيد) رفيعة للسماح بإجراء تسلسل وإلغاء تسلسل استدعاءات الوظائف:
using namespace std ::literals ;
using namespace zpp ::bits::literals ;
int foo ( int i, std::string s);
std::string bar ( int i, int j);
using rpc = zpp::bits::rpc<
zpp::bits::bind<foo, " foo " _sha256_int>,
zpp::bits::bind<bar, " bar " _sha256_int>
>;
auto [data, in, out] = zpp::bits::data_in_out();
// Server and client together:
auto [client, server] = rpc::client_server(in, out);
// Or separately:
rpc::client client{in, out};
rpc::server server{in, out};
// Request from the client:
client.request< " foo " _sha256_int>( 1337 , " hello " s).or_throw();
// Serve the request from the server:
server.serve().or_throw();
// Read back the response
client.response< " foo " _sha256_int>().or_throw(); // == foo(1337, "hello"s);
فيما يتعلق بمعالجة الأخطاء، كما هو الحال مع العديد من الأمثلة المذكورة أعلاه، يمكنك استخدام قيمة الإرجاع أو الاستثناءات أو طريقة zpp::throwing
لمعالجة الأخطاء.
// Return value based.
if ( auto result = client.request< " foo " _sha256_int>( 1337 , " hello " s); failure(result)) {
// Handle the failure.
}
if ( auto result = server.serve(); failure(result)) {
// Handle the failure.
}
if ( auto result = client.response< " foo " _sha256_int>(); failure(result)) {
// Handle the failure.
} else {
// Use response.value();
}
// Throwing based.
co_await client.request< " foo " _sha256_int>( 1337 , " hello " s); failure(result));
co_await server.serve();
co_await client.response< " foo " _sha256_int>(); // == foo(1337, "hello"s);
من الممكن أن يتم تخطي معرفات مكالمات RPC، على سبيل المثال، يتم تمريرها خارج النطاق، وإليك كيفية تحقيق ذلك:
server.serve(id); // id is already known, don't deserialize it.
client.request_body<Id>(arguments...); // request without serializing id.
يمكن أيضًا تسجيل وظائف الأعضاء لـ RPC، ومع ذلك يحتاج الخادم إلى الحصول على مرجع لكائن الفئة أثناء الإنشاء، ويجب أن تنتمي جميع وظائف الأعضاء إلى نفس الفئة (على الرغم من أنه من الممكن خلط وظائف نطاق مساحة الاسم):
struct a
{
int foo ( int i, std::string s);
};
std::string bar ( int i, int j);
using rpc = zpp::bits::rpc<
zpp::bits::bind<&a::foo, " a::foo " _sha256_int>,
zpp::bits::bind<bar, " bar " _sha256_int>
>;
auto [data, in, out] = zpp::bits::data_in_out();
// Our object.
a a1;
// Server and client together:
auto [client, server] = rpc::client_server(in, out, a1);
// Or separately:
rpc::client client{in, out};
rpc::server server{in, out, a1};
// Request from the client:
client.request< " a::foo " _sha256_int>( 1337 , " hello " s).or_throw();
// Serve the request from the server:
server.serve().or_throw();
// Read back the response
client.response< " a::foo " _sha256_int>().or_throw(); // == a1.foo(1337, "hello"s);
يمكن أن يعمل RPC أيضًا في وضع معتم ويسمح للوظيفة نفسها بإجراء تسلسل/إلغاء تسلسل البيانات، عند ربط دالة على أنها معتمة، باستخدام bind_opaque
:
// Each of the following signatures of `foo()` are valid for opaque rpc call:
auto foo (zpp::bits::in<> &, zpp::bits::out<> &);
auto foo (zpp::bits::in<> &);
auto foo (zpp::bits::out<> &);
auto foo (std::span<std::byte> input); // assumes all data is consumed from archive.
auto foo (std::span<std::byte> & input); // resize input in the function to signal how much was consumed.
using rpc = zpp::bits::rpc<
zpp::bits::bind_opaque<foo, " a::foo " _sha256_int>,
zpp::bits::bind<bar, " bar " _sha256_int>
>;
ترتيب البايت الافتراضي المستخدم هو المعالج الأصلي/نظام التشغيل المحدد. يمكنك اختيار ترتيب بايت آخر باستخدام zpp::bits::endian
أثناء الإنشاء كما يلي:
zpp::bits::in in (data, zpp::bits::endian::big{}); // Use big endian
zpp::bits::out out (data, zpp::bits::endian::big{}); // Use big endian
zpp::bits::in in (data, zpp::bits::endian::network{}); // Use big endian (provided for convenience)
zpp::bits::out out (data, zpp::bits::endian::network{}); // Use big endian (provided for convenience)
zpp::bits::in in (data, zpp::bits::endian::little{}); // Use little endian
zpp::bits::out out (data, zpp::bits::endian::little{}); // Use little endian
zpp::bits::in in (data, zpp::bits::endian::swapped{}); // If little use big otherwise little.
zpp::bits::out out (data, zpp::bits::endian::swapped{}); // If little use big otherwise little.
zpp::bits::in in (data, zpp::bits::endian::native{}); // Use the native one (default).
zpp::bits::out out (data, zpp::bits::endian::native{}); // Use the native one (default).
// Can also do it together, for example big endian:
auto [data, in, out] = data_in_out(zpp::bits::endian::big{});
auto [data, out] = data_out(zpp::bits::endian::big{});
auto [data, in] = data_in(zpp::bits::endian::big{});
على الطرف المتلقي (أرشيف الإدخال)، تدعم المكتبة أنواع العرض لأنواع البايت const، مثل std::span<const std::byte>
من أجل الحصول على عرض لجزء من البيانات دون نسخ. يجب استخدام هذا بعناية لأن إبطال مكررات البيانات المضمنة قد يتسبب في الاستخدام بعد الاستخدام المجاني. يتم توفيره للسماح بالتحسين عند الحاجة:
using namespace std ::literals ;
auto [data, in, out] = zpp::bits::data_in_out();
out ( " hello " sv).or_throw();
std::span< const std::byte> s;
in (s).or_throw();
// s.size() == "hello"sv.size()
// std::memcmp("hello"sv.data(), s.data(), "hello"sv.size()) == 0
}
هناك أيضًا نسخة غير بحجم، والتي تستهلك بقية بيانات الأرشيف للسماح بحالة الاستخدام الشائع للرأس ثم الكمية العشوائية من البيانات:
auto [data, in, out] = zpp::bits::data_in_out();
out (zpp::bits::unsized( " hello " sv)).or_throw();
std::span< const std::byte> s;
in (zpp::bits::unsized(s)).or_throw();
// s.size() == "hello"sv.size()
// std::memcmp("hello"sv.data(), s.data(), "hello"sv.size()) == 0
لا تدعم المكتبة إجراء تسلسل لقيم المؤشر الفارغة، ولكن لدعم مؤشرات الامتلاك الاختيارية بشكل صريح، مثل إنشاء الرسوم البيانية والهياكل المعقدة.
من الناحية النظرية، من الصحيح استخدام std::optional<std::unique_ptr<T>>
، ولكن يوصى باستخدام zpp::bits::optional_ptr<T>
المصمم خصيصًا والذي يعمل على تحسين القيمة المنطقية التي يحتفظ بها الكائن الاختياري عادةً، ويستخدم مؤشر فارغ كحالة غير صالحة.
سيؤدي تسلسل قيمة مؤشر فارغة في هذه الحالة إلى إجراء تسلسل بايت صفر، بينما يتم إجراء تسلسل القيم غير الخالية كبايت واحد متبوعًا بوحدات البايت الخاصة بالكائن. (على سبيل المثال، التسلسل مطابق لـ std::optional<T>
).
كجزء من تنفيذ المكتبة، كان مطلوبًا تنفيذ بعض أنواع الانعكاس، لإحصاء الأعضاء والأعضاء الزائرين، وتعرضها المكتبة للمستخدم:
struct point
{
int x;
int y;
};
# if !ZPP_BITS_AUTODETECT_MEMBERS_MODE
auto serialize (point) -> zpp::bits::members<2>;
# endif
static_assert (zpp::bits::number_of_members<point>() == 2);
constexpr auto sum = zpp::bits::visit_members(
point{ 1 , 2 }, []( auto x, auto y) { return x + y; });
static_assert (sum == 3 );
constexpr auto generic_sum = zpp::bits::visit_members(
point{ 1 , 2 }, []( auto ... members) { return ( 0 + ... + members); });
static_assert (generic_sum == 3 );
constexpr auto is_two_integers =
zpp::bits::visit_members_types<point>([]< typename ... Types>() {
if constexpr (std::same_as<std::tuple<Types...>,
std::tuple< int , int >>) {
return std::true_type{};
} else {
return std::false_type{};
}
})();
static_assert (is_two_integers);
يعمل المثال أعلاه مع أو بدون ZPP_BITS_AUTODETECT_MEMBERS_MODE=1
، اعتمادًا على #if
. كما هو مذكور أعلاه، يجب أن نعتمد على ميزة مترجم محددة لاكتشاف عدد الأعضاء التي قد لا تكون قابلة للنقل.
يمكن إنشاء الأرشيفات باستخدام خيارات تحكم إضافية مثل zpp::bits::append{}
الذي يوجه أرشيفات الإخراج لتعيين الموضع إلى نهاية المتجه أو مصدر بيانات آخر. (بالنسبة لأرشيفات الإدخال، ليس لهذا الخيار أي تأثير)
std::vector<std::byte> data;
zpp::bits::out out (data, zpp::bits::append{});
من الممكن استخدام عناصر تحكم متعددة واستخدامها أيضًا مع data_in_out/data_in/data_out/in_out
:
zpp::bits::out out (data, zpp::bits::append{}, zpp::bits::endian::big{});
auto [in, out] = in_out(data, zpp::bits::append{}, zpp::bits::endian::big{});
auto [data, in, out] = data_in_out(zpp::bits::size2b{}, zpp::bits::endian::big{});
يمكن أن يكون حجم التخصيص محدودًا في حالة أرشيف المخرجات إلى مخزن مؤقت متزايد أو عند استخدام أرشيف الإدخال لتحديد المدة التي يمكن أن تستغرقها الرسالة البادئة بطول واحد لتجنب تخصيص مخزن مؤقت كبير جدًا مقدمًا، وذلك باستخدام zpp::bits::alloc_limit<L>{}
. الاستخدام المقصود هو لأسباب تتعلق بالسلامة والصحة العامة وليس لقياس التخصيص الدقيق:
zpp::bits::out out (data, zpp::bits::alloc_limit< 0x10000 >{});
zpp::bits::in in (data, zpp::bits::alloc_limit< 0x10000 >{});
auto [in, out] = in_out(data, zpp::bits::alloc_limit< 0x10000 >{});
auto [data, in, out] = data_in_out(zpp::bits::alloc_limit< 0x10000 >{});
للحصول على أفضل صحة، عند استخدام المخزن المؤقت المتنامي للإخراج، إذا تمت زيادة المخزن المؤقت، يتم تغيير حجم المخزن المؤقت في النهاية للموضع الدقيق لأرشيف المخرجات، وهذا يؤدي إلى تغيير حجم إضافي وهو أمر مقبول في معظم الحالات، ولكن يمكنك تجنب ذلك تغيير حجم إضافي والتعرف على نهاية المخزن المؤقت باستخدام position()
. يمكنك تحقيق ذلك باستخدام zpp::bits::no_fit_size{}
:
zpp::bits::out out (data, zpp::bits::no_fit_size{});
للتحكم في تكبير متجه أرشيف الإخراج، يمكنك استخدام zpp::bits::enlarger<Mul, Div = 1>
:
zpp::bits::out out (data, zpp::bits::enlarger< 2 >{}); // Grow by multiplying size by 2.
zpp::bits::out out (data, zpp::bits::enlarger< 3 , 2 >{}); // Default - Grow by multiplying size by 3 and divide by 2 (enlarge by 1.5).
zpp::bits::out out (data, zpp::bits::exact_enlarger{}); // Grow to exact size every time.
بشكل افتراضي، ومن أجل السلامة، يقوم أرشيف المخرجات الذي يستخدم مخزنًا مؤقتًا متزايدًا بالتحقق من الفائض قبل نمو أي مخزن مؤقت. بالنسبة لأنظمة 64 بت، فإن هذا الفحص، على الرغم من كونه رخيصًا، يكاد يكون زائدًا عن الحاجة، حيث يكاد يكون من المستحيل تجاوز عدد صحيح 64 بت عندما يمثل حجم الذاكرة. (أي، سيفشل تخصيص الذاكرة قبل أن تقترب الذاكرة من تجاوز هذا العدد الصحيح). إذا كنت ترغب في تعطيل عمليات التحقق من التجاوز هذه، لصالح الأداء، فاستخدم: zpp::bits::no_enlarge_overflow{}
:
zpp::bits::out out (data, zpp::bits::no_enlarge_overflow{}); // Disable overflow check when enlarging.
عند إجراء تسلسل صريح، غالبًا ما يكون مطلوبًا تحديد ما إذا كان الأرشيف هو أرشيف إدخال أو إخراج، ويتم ذلك عبر وظيفة العضو الثابت archive.kind()
، ويمكن القيام به في if constexpr
:
static constexpr auto serialize ( auto & archive, auto & self)
{
using archive_type = std:: remove_cvref_t < decltype (archive)>;
if constexpr ( archive_type::kind () == zpp::bits::kind::in) {
// Input archive
} else if constexpr ( archive_type::kind () == zpp::bits::kind::out) {
// Output archive
} else {
// No such archive (no need to check for this)
}
}
توفر المكتبة نوعًا لإجراء تسلسل وإلغاء تسلسل الأعداد الصحيحة ذات الطول المتغير:
auto [data, in, out] = zpp::bits::data_in_out();
out (zpp::bits::varint{ 150 }).or_throw();
zpp::bits::varint i{ 0 };
in (i).or_throw();
// i == 150;
فيما يلي مثال على الترميز في وقت الترجمة:
static_assert (zpp::bits::to_bytes<zpp::bits::varint{ 150 }>() == "9601"_decode_hex);
تم توفير قالب الفئة zpp::bits::varint<T, E = varint_encoding::normal>
لتتمكن من تعريف أي نوع متكامل متغير أو نوع تعداد، إلى جانب الترميزات المحتملة zpp::bits::varint_encoding::normal/zig_zag
(العادي هو الافتراضي).
يتم توفير إعلانات الاسم المستعار التالية:
using vint32_t = varint<std:: int32_t >; // varint of int32 types.
using vint64_t = varint<std:: int64_t >; // varint of int64 types.
using vuint32_t = varint<std:: uint32_t >; // varint of unsigned int32 types.
using vuint64_t = varint<std:: uint64_t >; // varint of unsigned int64 types.
using vsint32_t = varint<std:: int32_t , varint_encoding::zig_zag>; // zig zag encoded varint of int32 types.
using vsint64_t = varint<std:: int64_t , varint_encoding::zig_zag>; // zig zag encoded varint of int64 types.
using vsize_t = varint<std:: size_t >; // varint of std::size_t types.
من الممكن أيضًا استخدام المتغيرات لتسلسل الأحجام افتراضيًا أثناء إنشاء الأرشيف:
auto [data, in, out] = data_in_out(zpp::bits::size_varint{});
zpp::bits::in in (data, zpp::bits::size_varint{}); // Uses varint to encode size.
zpp::bits::out out (data, zpp::bits::size_varint{}); // Uses varint to encode size.
لا يعتمد تنسيق التسلسل لهذه المكتبة على أي تنسيق معروف أو مقبول. وبطبيعة الحال، لا تدعم اللغات الأخرى هذا التنسيق، مما يجعل من المستحيل تقريبًا استخدام المكتبة للتواصل بين لغات البرمجة.
ولهذا السبب تدعم المكتبة تنسيق protobuf المتوفر بالعديد من اللغات.
يرجى ملاحظة أن دعم protobuf هو نوع من النوع التجريبي، مما يعني أنه قد لا يتضمن كل ميزات protobuf المحتملة، وهو أبطأ بشكل عام (حوالي 2-5 مرات أبطأ، معظمها عند إلغاء التسلسل) من التنسيق الافتراضي، الذي يهدف إلى أن يكون صفرًا.
نبدأ بالرسالة الأساسية:
struct example
{
zpp::bits:: vint32_t i; // varint of 32 bit, field number is implicitly set to 1,
// next field is implicitly 2, and so on
};
// Serialize as protobuf protocol (as usual, can also define this inside the class
// with `using serialize = zpp::bits::pb_protocol;`)
auto serialize ( const example &) -> zpp::bits::pb_protocol;
// Use archives as usual, specify what kind of size to prefix the message with.
// We chose no size to demonstrate the actual encoding of the message, but in general
// it is recommended to size prefix protobuf messages since they are not self terminating.
auto [data, in, out] = data_in_out(zpp::bits::no_size{});
out (example{. i = 150 }).or_throw();
example e;
in (e).or_throw();
// e.i == 150
// Serialize the message without any size prefix, and check the encoding at compile time:
static_assert (
zpp::bits::to_bytes<zpp::bits:: unsized_t <example>{{. i = 150 }}>() ==
"089601"_decode_hex);
للحصول على الصيغة الكاملة، والتي سنستخدمها لاحقًا لتمرير المزيد من الخيارات، استخدم zpp::bits::protocol
:
// Serialize as protobuf protocol (as usual, can also define this inside the class
// with `using serialize = zpp::bits::protocol<zpp::bits::pb{}>;`)
auto serialize ( const example &) -> zpp::bits::protocol<zpp::bits::pb{}>;
لحجز الحقول:
struct example
{
[[no_unique_address]] zpp::bits::pb_reserved _1; // field number 1 is reserved.
zpp::bits:: vint32_t i; // field number == 2
zpp::bits:: vsint32_t j; // field number == 3
};
لتحديد رقم الحقل بشكل صريح لكل عضو:
struct example
{
zpp::bits::pb_field<zpp::bits:: vint32_t , 20 > i; // field number == 20
zpp::bits::pb_field<zpp::bits:: vsint32_t , 30 > j; // field number == 30
using serialize = zpp::bits::pb_protocol;
};
غالبًا ما يكون الوصول إلى القيمة خلف الحقل شفافًا، ولكن إذا لزم الأمر بشكل صريح، استخدم pb_value(<variable>)
للحصول على القيمة أو تعيينها.
لتعيين الأعضاء إلى رقم حقل آخر:
struct example
{
zpp::bits:: vint32_t i; // field number == 20
zpp::bits:: vsint32_t j; // field number == 30
using serialize = zpp::bits::protocol<
zpp::bits::pb{
zpp::bits::pb_map< 1 , 20 >{}, // Map first member to field number 20.
zpp::bits::pb_map< 2 , 30 >{}}>; // Map second member to field number 30.
};
الأعضاء الثابتون هم ببساطة أعضاء بيانات C++ عاديون:
struct example
{
std:: uint32_t i; // fixed unsigned integer 32, field number == 1
};
كما هو الحال مع zpp::bits::members
، لأنه عندما يكون ذلك مطلوبًا، يمكنك تحديد عدد الأعضاء في حقل البروتوكول باستخدام zpp::bits::pb_members<N>
:
struct example
{
using serialize = zpp::bits::pb_members< 1 >; // 1 member.
zpp::bits:: vint32_t i; // field number == 1
};
تتضمن النسخة الكاملة مما سبق تمرير عدد الأعضاء كمعلمة ثانية للبروتوكول:
struct example
{
using serialize = zpp::bits::protocol<zpp::bits::pb{}, 1 >; // 1 member.
zpp::bits:: vint32_t i; // field number == 1
};
يتم ببساطة تضمين الرسائل المضمنة داخل الفصل كأعضاء بيانات:
struct nested_example
{
example nested; // field number == 1
};
auto serialize ( const nested_example &) -> zpp::bits::pb_protocol;
static_assert (zpp::bits::to_bytes<zpp::bits:: unsized_t <nested_example>{
{. nested = example{ 150 }}}>() == "0a03089601"_decode_hex);
الحقول المتكررة هي من شكل امتلاك الحاويات:
struct repeating
{
using serialize = zpp::bits::pb_protocol;
std::vector<zpp::bits:: vint32_t > integers; // field number == 1
std::string characters; // field number == 2
std::vector<example> examples; // repeating examples, field number == 3
};
حاليًا، جميع الحقول اختيارية، وهي ممارسة جيدة، ويتم إسقاط الحقول المفقودة وعدم ربطها بالرسالة، لتحقيق الكفاءة. أي قيمة لم يتم تعيينها في الرسالة تترك عضو البيانات الهدف سليمًا، مما يسمح بتنفيذ الإعدادات الافتراضية لأعضاء البيانات باستخدام مُهيئ عضو البيانات غير الثابت أو تهيئة عضو البيانات قبل إلغاء تسلسل الرسالة.
لنأخذ ملف .proto
كاملًا ونقوم بترجمته:
syntax = "proto3" ;
package tutorial ;
message person {
string name = 1 ;
int32 id = 2 ;
string email = 3 ;
enum phone_type {
mobile = 0 ;
home = 1 ;
work = 2 ;
}
message phone_number {
string number = 1 ;
phone_type type = 2 ;
}
repeated phone_number phones = 4 ;
}
message address_book {
repeated person people = 1 ;
}
الملف المترجم:
struct person
{
std::string name; // = 1
zpp::bits:: vint32_t id; // = 2
std::string email; // = 3
enum phone_type
{
mobile = 0 ,
home = 1 ,
work = 2 ,
};
struct phone_number
{
std::string number; // = 1
phone_type type; // = 2
};
std::vector<phone_number> phones; // = 4
};
struct address_book
{
std::vector<person> people; // = 1
};
auto serialize ( const person &) -> zpp::bits::pb_protocol;
auto serialize ( const person::phone_number &) -> zpp::bits::pb_protocol;
auto serialize ( const address_book &) -> zpp::bits::pb_protocol;
إلغاء تسلسل الرسالة التي تم تسلسلها في الأصل باستخدام بايثون:
import addressbook_pb2
person = addressbook_pb2 . person ()
person . id = 1234
person . name = "John Doe"
person . email = "[email protected]"
phone = person . phones . add ()
phone . number = "555-4321"
phone . type = addressbook_pb2 . person . home
الناتج الذي نحصل عليه person
هو:
name : "John Doe"
id : 1234
email : "[email protected]"
phones {
number : "555-4321"
type : home
}
لنقم بتسلسلها:
person . SerializeToString ()
والنتيجة هي:
b' n x08 John Doe x10 xd2 t x1a x10 [email protected]" x0c n x08 555-4321 x10 x01 '
العودة إلى لغة C++:
using namespace zpp ::bits::literals ;
constexpr auto data =
" nx08 John Doe x10xd2tx1ax10 [email protected] "x0cnx08 "
" 555-4321 x10x01 " _b;
static_assert (data.size() == 45);
person p;
zpp::bits::in{data, zpp::bits::no_size{}}(p).or_throw();
// p.name == "John Doe"
// p.id == 1234
// p.email == "[email protected]"
// p.phones.size() == 1
// p.phones[0].number == "555-4321"
// p.phones[0].type == person::home
بشكل افتراضي، يتم تضمين zpp::bits
بقوة، ولكن لتقليل حجم التعليمات البرمجية، لا يتم تضمين فك التشفير الكامل للمتغيرات (الأعداد الصحيحة المتغيرة الطول). لتكوين تضمين فك تشفير المتغير الكامل، حدد ZPP_BITS_INLINE_DECODE_VARINT=1
.
إذا كنت تشك في أن zpp::bits
يتم تضمينها كثيرًا لدرجة أنها تؤثر بشكل سيء على حجم الكود، فيمكنك تعريف ZPP_BITS_INLINE_MODE=0
، مما يؤدي إلى تعطيل كل قوة التضمين ومراقبة النتائج. عادة ما يكون له تأثير ضئيل، ولكن يتم توفيره كما هو للتحكم الإضافي.
في بعض المترجمين، قد تجد دائمًا فشلًا مضمنًا مع الهياكل العودية (على سبيل المثال الرسم البياني الشجري). في هذه الحالات، يكون مطلوبًا بطريقة ما تجنب السمة المضمنة دائمًا للبنية المحددة، ومن الأمثلة التافهة استخدام وظيفة تسلسل صريحة، على الرغم من أن المكتبة تكتشف مثل هذه المناسبات في معظم الأوقات وليس من الضروري، ولكن يتم تقديم المثال فقط في حالة:
struct node
{
constexpr static auto serialize ( auto & archive, auto & node)
{
return archive (node. value , node. nodes );
}
int value;
std::vector<node> nodes;
};
مكتبة | حالة الاختبار | حجم بن | حجم البيانات | وقت الخدمة | الوقت المناسب |
---|---|---|---|---|---|
zpp_bits | عام | 52192ب | 8413 ب | 733 مللي ثانية | 693 مللي ثانية |
zpp_bits | عازلة ثابتة | 48000ب | 8413 ب | 620 مللي ثانية | 667 مللي ثانية |
bitsery | عام | 70904ب | 6913 ب | 1470 مللي ثانية | 1524 مللي ثانية |
bitsery | عازلة ثابتة | 53648ب | 6913 ب | 927 مللي ثانية | 1466 مللي ثانية |
يعزز | عام | 279024ب | 11037 ب | 15126 مللي ثانية | 12724 مللي ثانية |
رقائق الذرة | عام | 70560ب | 10413 ب | 10777 مللي ثانية | 9088 مللي ثانية |
مخازن مسطحة | عام | 70640ب | 14924ب | 8757 مللي ثانية | 3361 مللي ثانية |
بخط اليد | عام | 47936ب | 10413 ب | 1506 مللي ثانية | 1577 مللي ثانية |
بخط اليد | غير آمن | 47944ب | 10413 ب | 1616 مللي ثانية | 1392 مللي ثانية |
com.iostream | عام | 53872ب | 8413 ب | 11956 مللي ثانية | 12928 مللي ثانية |
com.msgpack | عام | 89144ب | 8857 ب | 2770 مللي ثانية | 14033 مللي ثانية |
بروتوبوف | عام | 2077864ب | 10018ب | 19929 مللي ثانية | 20592 مللي ثانية |
بروتوبوف | الساحة | 2077872ب | 10018ب | 10319 مللي ثانية | 11787 مللي ثانية |
ياس | عام | 61072ب | 10463 ب | 2286 مللي ثانية | 1770 مللي ثانية |
مكتبة | حالة الاختبار | حجم بن | حجم البيانات | وقت الخدمة | الوقت المناسب |
---|---|---|---|---|---|
zpp_bits | عام | 47128ب | 8413 ب | 790 مللي ثانية | 715 مللي ثانية |
zpp_bits | عازلة ثابتة | 43056ب | 8413 ب | 605 مللي ثانية | 694 مللي ثانية |
bitsery | عام | 53728ب | 6913 ب | 2128 مللي ثانية | 1832 مللي ثانية |
bitsery | عازلة ثابتة | 49248ب | 6913 ب | 946 مللي ثانية | 1941 مللي ثانية |
يعزز | عام | 237008ب | 11037 ب | 16011 مللي ثانية | 13017 مللي ثانية |
رقائق الذرة | عام | 61480ب | 10413 ب | 9977 مللي ثانية | 8565 مللي ثانية |
مخازن مسطحة | عام | 62512ب | 14924ب | 9812 مللي ثانية | 3472 مللي ثانية |
بخط اليد | عام | 43112ب | 10413 ب | 1391 مللي ثانية | 1321 مللي ثانية |
بخط اليد | غير آمن | 43120ب | 10413ب | 1393 مللي ثانية | 1212 مللي ثانية |
com.iostream | عام | 48632ب | 8413 ب | 10992 مللي ثانية | 12771 مللي ثانية |
com.msgpack | عام | 77384ب | 8857 ب | 3563 مللي ثانية | 14705 مللي ثانية |
بروتوبوف | عام | 2032712ب | 10018ب | 18125 مللي ثانية | 20211 مللي ثانية |
بروتوبوف | الساحة | 2032760ب | 10018ب | 9166 مللي ثانية | 11378 مللي ثانية |
ياس | عام | 51000ب | 10463ب | 2114 مللي ثانية | 1558 مللي ثانية |
أتمنى أن تجد هذه المكتبة مفيدة. لا تتردد في إرسال أي مشاكل، وتقديم اقتراحات للتحسينات، وما إلى ذلك.