Eine moderne binäre C++20-Serialisierungs- und RPC-Bibliothek mit nur einer Header-Datei.
Diese Bibliothek ist ein Nachfolger von zpp::serializer. Die Bibliothek versucht, die Verwendung einfacher zu gestalten, verfügt jedoch über eine mehr oder weniger ähnliche API wie ihr Vorgänger.
constexpr
zpp::bits
lässt sich beispielsweise einfacher eingeben als zpp::serializer
.zpp::serializer
mit festen 8 Bytes sha1-Serialisierungs-ID. Bei vielen Typen ist die Aktivierung der Serialisierung transparent und erfordert keine zusätzlichen Codezeilen. Diese Typen müssen vom Aggregattyp sein und keine Array-Mitglieder enthalten. Hier ist ein Beispiel einer person
mit Name und Alter:
struct person
{
std::string name;
int age{};
};
Beispiel für die Serialisierung der Person in und aus einem Byte-Vektor:
// 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);
Dieses Beispiel funktioniert fast, wir werden gewarnt, dass wir den Rückgabewert verwerfen. Zur Fehlerprüfung lesen Sie weiter.
Wir müssen nach Fehlern suchen. Die Bibliothek bietet dazu mehrere Möglichkeiten – eine auf dem Rückgabewert basierende, eine ausnahmebasierte oder eine zpp::throwing-basierte Methode.
Die auf dem Rückgabewert basierende Methode ist am explizitesten oder wenn Sie einfach nur Rückgabewerte bevorzugen:
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.
}
Die auf Ausnahmen basierende Methode mit .or_throw()
(lesen Sie dies als „Erfolgreich sein oder werfen“ – daher 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 ;
});
}
Eine weitere Option ist zpp::throwing, bei der die Fehlerprüfung in zwei einfache co_await
s umgewandelt wird. Um zu verstehen, wie man auf Fehler prüft, stellen wir eine vollständige Hauptfunktion bereit:
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 ;
});
}
Alle oben genannten Methoden verwenden intern die folgenden Fehlercodes und können mithilfe des Vergleichsoperators von return value based oder durch Untersuchen des internen Fehlercodes von std::system_error
oder zpp::throwing
überprüft werden, je nachdem, welche Methode Sie verwendet haben:
std::errc::result_out_of_range
– Versuch, aus einem zu kurzen Puffer zu schreiben oder zu lesen.std::errc::no_buffer_space
– wachsender Puffer würde über die Zuordnungsgrenzen hinaus wachsen oder überlaufen.std::errc::value_too_large
– Die Varint-Kodierung (Ganzzahl mit variabler Länge) liegt außerhalb der Darstellungsgrenzen.std::errc::message_size
– Die Nachrichtengröße liegt außerhalb der benutzerdefinierten Zuordnungsgrenzen.std::errc::not_supported
– Versuch, einen RPC aufzurufen, der nicht als unterstützt aufgeführt ist.std::errc::bad_message
– Versuch, eine Variante eines unbekannten Typs zu lesen.std::errc::invalid_argument
– Versuch, einen Nullzeiger oder eine wertlose Variante zu serialisieren.std::errc::protocol_error
– Versuch, eine ungültige Protokollnachricht zu deserialisieren. Bei den meisten Nicht-Aggregattypen (oder Aggregattypen mit Array-Mitgliedern) ist die Aktivierung der Serialisierung einzeilig. Hier ist ein Beispiel für eine nicht aggregierte 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{};
};
Die meisten Typen, die wir serialisieren, können mit strukturierter Bindung arbeiten, und diese Bibliothek nutzt dies aus, aber Sie müssen die Anzahl der Mitglieder in Ihrer Klasse angeben, damit dies mit der oben beschriebenen Methode funktioniert.
Dies funktioniert auch mit der argumentabhängigen Suche, sodass die Quellklasse nicht geändert werden kann:
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
In einigen Compilern arbeitet SFINAE mit requires expression
unter if constexpr
und unevaluated lambda expression
. Dies bedeutet, dass auch bei nicht aggregierten Typen die Anzahl der Mitglieder automatisch erkannt werden kann, wenn sich alle Mitglieder in derselben Struktur befinden. Um sich anzumelden, definieren Sie 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{};
};
Dies funktioniert mit clang 13
, allerdings ist die Portabilität davon nicht klar, da es in gcc
nicht funktioniert (es ist ein schwerwiegender Fehler) und im Standard ausdrücklich angegeben ist, dass die Absicht besteht, SFINAE in ähnlichen Fällen nicht zuzulassen, also ist es so ist standardmäßig deaktiviert.
Wenn Ihre Datenelemente oder Ihr Standardkonstruktor privat sind, müssen Sie sich wie folgt mit zpp::bits::access
anfreunden:
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{};
};
Um das Speichern und Laden eines beliebigen Objekts mithilfe der expliziten Serialisierung zu ermöglichen, was unabhängig von der Kompatibilität mit strukturierten Bindungen funktioniert, fügen Sie Ihrer Klasse die folgenden Zeilen hinzu:
constexpr static auto serialize ( auto & archive, auto & self)
{
return archive (self. object_1 , self. object_2 , ...);
}
Beachten Sie, dass object_1, object_2, ...
die nicht statischen Datenelemente Ihrer Klasse sind.
Hier ist noch einmal das Beispiel einer Personenklasse mit expliziter Serialisierungsfunktion:
struct person
{
constexpr static auto serialize ( auto & archive, auto & self)
{
return archive (self. name , self. age );
}
std::string name;
int age{};
};
Oder mit argumentabhängiger Suche:
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
Ein- und Ausgabearchive gemeinsam und getrennt von Daten erstellen:
// 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();
Archive können aus einem der folgenden Bytetypen erstellt werden:
// 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);
Sie können auch Datenobjekte fester Größe wie Array, std::array
und Ansichtstypen wie std::span
ähnlich wie oben verwenden. Sie müssen nur sicherstellen, dass die Größe ausreicht, da die Größe nicht geändert werden kann:
// 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);
Bei Verwendung eines Vektors oder Strings wächst dieser automatisch auf die richtige Größe, bei den oben genannten sind die Daten jedoch auf die Grenzen der Arrays oder Spans beschränkt.
Wenn Sie das Archiv auf eine der oben genannten Arten erstellen, können Sie eine Vielzahl von Parametern übergeben, die das Archivverhalten steuern, z. B. die Bytereihenfolge, Standardgrößentypen, die Festlegung des Anhängeverhaltens usw. Dies wird im Rest der README-Datei besprochen.
Wie oben erwähnt, ist die Bibliothek fast vollständig constexpr. Hier ist ein Beispiel für die Verwendung eines Arrays als Datenobjekt, aber auch für die Verwendung in der Kompilierungszeit, um ein Tupel von Ganzzahlen zu serialisieren und zu deserialisieren:
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 });
Der Einfachheit halber bietet die Bibliothek auch einige vereinfachte Serialisierungsfunktionen für die Kompilierungszeit:
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 });
Fragen Sie mit position()
die Position von in
und out
, also die jeweils gelesenen und geschriebenen Bytes:
std:: size_t bytes_read = in.position();
std:: size_t bytes_written = out.position();
Setzen Sie die Position nach hinten oder vorne oder auf den Anfang zurück, verwenden Sie sie mit äußerster Vorsicht:
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.
Bei der Serialisierung von Standardbibliothekstypen variabler Länge wie Vektoren, Zeichenfolgen und Ansichtstypen wie Span und String View speichert die Bibliothek zunächst eine 4-Byte-Ganzzahl, die die Größe darstellt, gefolgt von den Elementen.
std::vector v = { 1 , 2 , 3 , 4 };
out (v);
in (v);
Der Grund dafür, dass der Standardgrößentyp 4 Byte beträgt (d. h. std::uint32_t
), liegt in der Portabilität zwischen verschiedenen Architekturen, und die meisten Programme erreichen fast nie den Fall, dass ein Container mehr als 2^32 Elemente umfasst, und das kann auch der Fall sein Es ist ungerecht, den Preis für die Standardgröße von 8 Bytes zu zahlen.
Für bestimmte Größentypen, die nicht 4 Bytes groß sind, verwenden Sie zpp::bits::sized
/ zpp::bits::sized_t
wie folgt:
// 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);
Stellen Sie sicher, dass der Größentyp groß genug für das serialisierte Objekt ist. Andernfalls werden gemäß den Konvertierungsregeln für vorzeichenlose Typen weniger Elemente serialisiert.
Sie können sich auch dafür entscheiden, die Größe überhaupt nicht zu serialisieren, etwa so:
// 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);
Wo es üblich ist, gibt es Alias-Deklarationen für Versionen von Typen mit/ohne Größe, hier sind es beispielsweise vector
und span
, andere wie string
, string_view
usw. verwenden dasselbe Muster.
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.
Die Serialisierung von Typen fester Größe wie Arrays, std::array
s, std::tuple
s beinhaltet keinen Overhead mit Ausnahme der Elemente, auf die einander folgt.
Während der Erstellung ist es möglich, den Standardgrößentyp für das gesamte Archiv zu ändern:
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{});
Bei den meisten Typen weiß die Bibliothek, wie sie Objekte als Bytes optimiert und serialisiert. Es ist jedoch deaktiviert, wenn explizite Serialisierungsfunktionen verwendet werden.
Wenn Sie wissen, dass Ihr Typ genauso serialisierbar ist wie Rohbytes, und Sie die explizite Serialisierung verwenden, können Sie sich dafür entscheiden und seine Serialisierung auf ein bloßes memcpy
optimieren:
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));
}
};
Es ist auch möglich, dies direkt von einem Vektor oder einer Spanne trivial kopierbarer Typen aus zu tun. Dieses Mal verwenden wir bytes
anstelle von as_bytes
, da wir den Inhalt des Vektors in Bytes konvertieren und nicht das Vektorobjekt selbst (die Daten, auf die der Vektor zeigt, und nicht). das Vektorobjekt):
std::vector<point> points;
out (zpp::bits::bytes(points));
in (zpp::bits::bytes(points));
In diesem Fall wird die Größe jedoch nicht serialisiert. Dies kann in Zukunft erweitert werden, um auch die Serialisierung der Größe ähnlich wie bei anderen Ansichtstypen zu unterstützen. Wenn Sie in Bytes serialisieren müssen und die Größe benötigen, können Sie als Workaround eine Umwandlung in std::span<std::byte>
durchführen.
Obwohl es aufgrund des Null-Overheads der Serialisierung kein perfektes Tool für die Abwärtskompatibilität von Strukturen gibt, können Sie std::variant
verwenden, um Ihre Klassen zu versionieren oder ein nettes, auf Polymorphismus basierendes Dispatching zu erstellen. Gehen Sie dazu wie folgt vor:
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
Und dann zur Serialisierung selbst:
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);
Die Art und Weise, wie die Variante serialisiert wird, besteht darin, ihren Index (0 oder 1) als std::byte
zu serialisieren, bevor das eigentliche Objekt serialisiert wird. Dies ist sehr effizient, allerdings möchten Benutzer manchmal eine explizite Serialisierungs-ID dafür wählen, siehe den folgenden Punkt
Um eine benutzerdefinierte Serialisierungs-ID festzulegen, müssen Sie jeweils eine zusätzliche Zeile innerhalb bzw. außerhalb Ihrer Klasse hinzufügen:
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>;
Beachten Sie, dass die Serialisierungs-IDs der Typen in der Variante in der Länge übereinstimmen müssen, da sonst ein Kompilierungsfehler auftritt.
Sie können anstelle einer lesbaren Zeichenfolge auch eine beliebige Bytefolge sowie eine Ganzzahl oder einen beliebigen Literaltyp verwenden. Hier ist ein Beispiel für die Verwendung eines Hashs einer Zeichenfolge als Serialisierungs-ID:
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
Sie können auch nur die ersten Bytes des Hashs serialisieren, etwa so:
// 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>;
Der Typ wird dann zur Kompilierungszeit mit (... warte darauf) zpp::bits::out
zur Kompilierungszeit in Bytes konvertiert. Solange Ihr Literaltyp also gemäß den oben genannten Punkten serialisierbar ist, können Sie ihn als verwenden Serialisierungs-ID. Die ID wird in std::array<std::byte, N>
serialisiert, für 1, 2, 4 und 8 Bytes ist der zugrunde liegende Typ jedoch std::byte
std::uint16_t
, std::uin32_t
und std::uint64_t
bzw. für Benutzerfreundlichkeit und Effizienz.
Wenn Sie die Variante ohne ID serialisieren möchten oder wenn Sie wissen, dass eine Variante beim Deserialisieren eine bestimmte ID haben wird, können Sie dies mit zpp::bits::known_id
tun, um Ihre Variante zu verpacken:
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));
Beschreibung der Hilfsliterale in der Bibliothek:
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
verwenden. Die Funktion darf keine Vorlage sein und genau eine Überladung haben: 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 ;
}
Wenn Ihre Funktion keine Parameter empfängt, ruft sie lediglich die Funktion ohne Deserialisierung auf und der Rückgabewert ist der Rückgabewert Ihrer Funktion. Wenn die Funktion void zurückgibt, gibt es keinen Wert für den resultierenden Typ.
Die Bibliothek bietet außerdem eine schlanke RPC-Schnittstelle (Remote Procedure Call), um Funktionsaufrufe serialisieren und deserialisieren zu können:
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);
Was die Fehlerbehandlung betrifft, können Sie, ähnlich wie in vielen Beispielen oben, Rückgabewerte, Ausnahmen oder zpp::throwing
zur Fehlerbehandlung verwenden.
// 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);
Es ist möglich, dass die IDs der RPC-Aufrufe übersprungen werden, beispielsweise wenn sie außerhalb des Bandes weitergeleitet werden. Hier erfahren Sie, wie Sie dies erreichen:
server.serve(id); // id is already known, don't deserialize it.
client.request_body<Id>(arguments...); // request without serializing id.
Mitgliedsfunktionen können auch für RPC registriert werden, allerdings muss der Server während der Erstellung einen Verweis auf das Klassenobjekt erhalten und alle Mitgliedsfunktionen müssen zur gleichen Klasse gehören (obwohl Namespace-Bereichsfunktionen gemischt werden können):
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);
Der RPC kann auch in einem undurchsichtigen Modus arbeiten und die Funktion selbst die Daten serialisieren/deserialisieren lassen, wenn eine Funktion mit bind_opaque
als undurchsichtig gebunden wird:
// 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>
>;
Die standardmäßig verwendete Bytereihenfolge ist die des ausgewählten nativen Prozessors/Betriebssystems. Sie können während der Erstellung mit zpp::bits::endian
eine andere Bytereihenfolge wählen, etwa so:
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{});
Auf der Empfangsseite (Eingabearchiv) unterstützt die Bibliothek Ansichtstypen von const-Byte-Typen, wie z. B. std::span<const std::byte>
um eine Ansicht eines Teils der Daten ohne Kopieren zu erhalten. Dies muss mit Vorsicht verwendet werden, da die Invalidierung von Iteratoren der enthaltenen Daten zu einer späteren Verwendung führen könnte. Es wird bereitgestellt, um bei Bedarf eine Optimierung zu ermöglichen:
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
}
Es gibt auch eine Version ohne Größe, die den Rest der Archivdaten verbraucht, um den allgemeinen Anwendungsfall von Header und dann beliebiger Datenmenge zu ermöglichen:
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
Die Bibliothek unterstützt die Serialisierung von Nullzeigerwerten nicht, unterstützt jedoch ausdrücklich optionale besitzende Zeiger, beispielsweise zum Erstellen von Diagrammen und komplexen Strukturen.
Theoretisch ist die Verwendung von std::optional<std::unique_ptr<T>>
zulässig, es wird jedoch empfohlen, das speziell erstellte zpp::bits::optional_ptr<T>
zu verwenden, das den booleschen Wert optimiert, den das optionale Objekt normalerweise behält. und verwendet einen Nullzeiger als ungültigen Zustand.
Durch die Serialisierung eines Nullzeigerwerts wird in diesem Fall ein Null-Byte serialisiert, während Nicht-Null-Werte als einzelnes Eins-Byte serialisiert werden, gefolgt von den Bytes des Objekts. (d. h. die Serialisierung ist identisch mit std::optional<T>
).
Als Teil der Bibliotheksimplementierung war es erforderlich, einige Reflexionstypen zum Zählen von Mitgliedern und besuchenden Mitgliedern zu implementieren, und die Bibliothek stellt diese dem Benutzer zur Verfügung:
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);
Das obige Beispiel funktioniert mit oder ohne ZPP_BITS_AUTODETECT_MEMBERS_MODE=1
, abhängig vom #if
. Wie oben erwähnt, müssen wir uns auf eine bestimmte Compilerfunktion verlassen, um die Anzahl der Mitglieder zu ermitteln, die möglicherweise nicht portierbar sind.
Archive können mit zusätzlichen Steueroptionen wie zpp::bits::append{}
erstellt werden, die Ausgabearchive anweisen, die Position auf das Ende des Vektors oder einer anderen Datenquelle festzulegen. (Für Eingabearchive hat diese Option keine Auswirkung)
std::vector<std::byte> data;
zpp::bits::out out (data, zpp::bits::append{});
Es ist möglich, mehrere Steuerelemente zu verwenden und diese auch mit data_in_out/data_in/data_out/in_out
zu verwenden:
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{});
Die Zuweisungsgröße kann im Falle eines Ausgabearchivs auf einen wachsenden Puffer oder bei Verwendung eines Eingabearchivs begrenzt werden, um die Länge einer Präfixnachricht mit einfacher Länge zu begrenzen, um die Zuweisung eines sehr großen Puffers im Voraus zu vermeiden, indem zpp::bits::alloc_limit<L>{}
verwendet wird zpp::bits::alloc_limit<L>{}
. Die beabsichtigte Verwendung dient eher Sicherheits- und Gesundheitsgründen als einer genauen Zuordnungsmessung:
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 >{});
Um die größtmögliche Korrektheit zu gewährleisten: Wenn Sie einen wachsenden Puffer für die Ausgabe verwenden, wird die Größe des Puffers am Ende entsprechend der genauen Position des Ausgabearchivs angepasst, wenn der Puffer vergrößert wurde. Dies führt zu einer zusätzlichen Größenänderung, die in den meisten Fällen akzeptabel ist, Sie können dies jedoch vermeiden Zusätzliche Größenänderung und Erkennung des Pufferendes mithilfe von position()
. Sie können dies erreichen, indem Sie zpp::bits::no_fit_size{}
verwenden:
zpp::bits::out out (data, zpp::bits::no_fit_size{});
Um die Vergrößerung des Ausgabearchivvektors zu steuern, können Sie zpp::bits::enlarger<Mul, Div = 1>
verwenden:
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.
Aus Sicherheitsgründen prüft ein Ausgabearchiv, das einen wachsenden Puffer verwendet, standardmäßig auf Überlauf, bevor ein Puffer wächst. Bei 64-Bit-Systemen ist diese Prüfung zwar kostengünstig, aber nahezu überflüssig, da es nahezu unmöglich ist, eine 64-Bit-Ganzzahl zu überlaufen, wenn sie eine Speichergröße darstellt. (d. h. die Speicherzuweisung schlägt fehl, bevor der Speicher nahe daran ist, diese Ganzzahl zu überlaufen). Wenn Sie diese Überlaufprüfungen zugunsten der Leistung deaktivieren möchten, verwenden Sie: zpp::bits::no_enlarge_overflow{}
:
zpp::bits::out out (data, zpp::bits::no_enlarge_overflow{}); // Disable overflow check when enlarging.
Bei der expliziten Serialisierung ist es häufig erforderlich, zu identifizieren, ob das Archiv ein Eingabe- oder Ausgabearchiv ist. Dies erfolgt über die statische Memberfunktion archive.kind()
und kann in einem if constexpr
erfolgen:
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)
}
}
Die Bibliothek stellt einen Typ zum Serialisieren und Deserialisieren von Ganzzahlen variabler Länge bereit:
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;
Hier ist ein Beispiel für die Codierung zur Kompilierzeit:
static_assert (zpp::bits::to_bytes<zpp::bits::varint{ 150 }>() == "9601"_decode_hex);
Die Klassenvorlage zpp::bits::varint<T, E = varint_encoding::normal>
wird bereitgestellt, um jeden varint-Integraltyp oder Aufzählungstyp zusammen mit möglichen Codierungen definieren zu können zpp::bits::varint_encoding::normal/zig_zag
(normal ist Standard).
Die folgenden Alias-Deklarationen werden bereitgestellt:
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.
Die standardmäßige Verwendung von Varints zur Serialisierung von Größen ist auch während der Archiverstellung möglich:
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.
Das Serialisierungsformat dieser Bibliothek basiert nicht auf einem bekannten oder akzeptierten Format. Natürlich unterstützen andere Sprachen dieses Format nicht, was es nahezu unmöglich macht, die Bibliothek für die programmiersprachenübergreifende Kommunikation zu verwenden.
Aus diesem Grund unterstützt die Bibliothek das Protobuf-Format, das in vielen Sprachen verfügbar ist.
Bitte beachten Sie, dass die Protobuf-Unterstützung eher experimentell ist, was bedeutet, dass sie möglicherweise nicht alle möglichen Protobuf-Funktionen umfasst und im Allgemeinen langsamer ist (etwa zwei bis fünf Mal langsamer, hauptsächlich bei Deserialisierung) als das Standardformat, das auf einen Null-Overhead abzielt.
Beginnend mit der Grundbotschaft:
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);
Für die vollständige Syntax, die wir später zum Übergeben weiterer Optionen verwenden werden, verwenden Sie 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{}>;
Um Felder zu reservieren:
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
};
So geben Sie für jedes Mitglied explizit die Feldnummer an:
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;
};
Der Zugriff auf den Wert hinter dem Feld ist oft transparent. Bei explizitem Bedarf können Sie jedoch pb_value(<variable>)
verwenden, um den Wert abzurufen oder ihm zuzuweisen.
So ordnen Sie Elemente einer anderen Feldnummer zu:
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.
};
Feste Member sind einfach reguläre C++-Datenmember:
struct example
{
std:: uint32_t i; // fixed unsigned integer 32, field number == 1
};
Wie bei zpp::bits::members
können Sie bei Bedarf die Anzahl der Mitglieder im Protokollfeld mit zpp::bits::pb_members<N>
angeben:
struct example
{
using serialize = zpp::bits::pb_members< 1 >; // 1 member.
zpp::bits:: vint32_t i; // field number == 1
};
Die Vollversion des oben Gesagten beinhaltet die Übergabe der Anzahl der Mitglieder als zweiten Parameter an das Protokoll:
struct example
{
using serialize = zpp::bits::protocol<zpp::bits::pb{}, 1 >; // 1 member.
zpp::bits:: vint32_t i; // field number == 1
};
Eingebettete Nachrichten werden einfach als Datenelemente in der Klasse verschachtelt:
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);
Wiederholte Felder haben die Form von Besitzcontainern:
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
};
Derzeit sind alle Felder optional, was eine bewährte Vorgehensweise ist. Fehlende Felder werden gelöscht und aus Effizienzgründen nicht mit der Nachricht verkettet. Jeder Wert, der nicht in einer Nachricht festgelegt ist, lässt das Zieldatenelement intakt, was die Implementierung von Standardwerten für Datenelemente mithilfe eines nicht statischen Datenelementinitialisierers oder die Initialisierung des Datenelements vor dem Deserialisieren der Nachricht ermöglicht.
Nehmen wir eine vollständige .proto
Datei und übersetzen sie:
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 ;
}
Die übersetzte Datei:
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;
Dererialisieren einer Nachricht, die ursprünglich mit Python serialisiert wurde:
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
Die Ausgabe, die wir für person
erhalten, ist:
name : "John Doe"
id : 1234
email : "[email protected]"
phones {
number : "555-4321"
type : home
}
Lass es uns serialisieren:
person . SerializeToString ()
Das Ergebnis ist:
b' n x08 John Doe x10 xd2 t x1a x10 [email protected]" x0c n x08 555-4321 x10 x01 '
Zurück zu 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
Standardmäßig inline zpp::bits
aggressiv, aber um die Codegröße zu reduzieren, wird die vollständige Decodierung von Varints (Ganzzahlen variabler Länge) nicht inline ausgeführt. Um das Inlining der vollständigen Variantendekodierung zu konfigurieren, definieren Sie ZPP_BITS_INLINE_DECODE_VARINT=1
.
Wenn Sie den Verdacht haben, dass zpp::bits
zu viel Inlining durchführt, sodass es sich stark auf die Codegröße auswirkt, können Sie ZPP_BITS_INLINE_MODE=0
definieren, wodurch jegliches erzwungene Inlining deaktiviert wird, und die Ergebnisse beobachten. Normalerweise hat es einen vernachlässigbaren Effekt, wird aber zur zusätzlichen Kontrolle bereitgestellt.
Bei manchen Compilern kommt es möglicherweise vor, dass Always Inline bei rekursiven Strukturen (z. B. einem Baumdiagramm) fehlschlägt. In diesen Fällen ist es erforderlich, das Always-Inline-Attribut für die spezifische Struktur irgendwie zu vermeiden. Ein triviales Beispiel wäre die Verwendung einer expliziten Serialisierungsfunktion, obwohl die Bibliothek solche Fälle in den meisten Fällen erkennt und dies nicht notwendig ist, aber das Beispiel wird nur bereitgestellt falls:
struct node
{
constexpr static auto serialize ( auto & archive, auto & node)
{
return archive (node. value , node. nodes );
}
int value;
std::vector<node> nodes;
};
Bibliothek | Testfall | Behältergröße | Datengröße | Ser Zeit | der Zeit |
---|---|---|---|---|---|
zpp_bits | allgemein | 52192B | 8413B | 733 ms | 693 ms |
zpp_bits | fester Puffer | 48000B | 8413B | 620 ms | 667 ms |
bitsery | allgemein | 70904B | 6913B | 1470 ms | 1524 ms |
bitsery | fester Puffer | 53648B | 6913B | 927 ms | 1466 ms |
Schub | allgemein | 279024B | 11037B | 15126 ms | 12724 ms |
Getreide | allgemein | 70560B | 10413B | 10777 ms | 9088 ms |
Flachpuffer | allgemein | 70640B | 14924B | 8757 ms | 3361 ms |
handschriftlich | allgemein | 47936B | 10413B | 1506 ms | 1577 ms |
handschriftlich | unsicher | 47944B | 10413B | 1616 ms | 1392 ms |
iostream | allgemein | 53872B | 8413B | 11956 ms | 12928 ms |
msgpack | allgemein | 89144B | 8857B | 2770 ms | 14033 ms |
protobuf | allgemein | 2077864B | 10018B | 19929 ms | 20592 ms |
protobuf | Arena | 2077872B | 10018B | 10319 ms | 11787 ms |
Ja | allgemein | 61072B | 10463B | 2286 ms | 1770 ms |
Bibliothek | Testfall | Behältergröße | Datengröße | Ser Zeit | der Zeit |
---|---|---|---|---|---|
zpp_bits | allgemein | 47128B | 8413B | 790 ms | 715 ms |
zpp_bits | fester Puffer | 43056B | 8413B | 605 ms | 694 ms |
bitsery | allgemein | 53728B | 6913B | 2128 ms | 1832 ms |
bitsery | fester Puffer | 49248B | 6913B | 946 ms | 1941 ms |
Schub | allgemein | 237008B | 11037B | 16011 ms | 13017 ms |
Getreide | allgemein | 61480B | 10413B | 9977 ms | 8565 ms |
Flachpuffer | allgemein | 62512B | 14924B | 9812 ms | 3472 ms |
handschriftlich | allgemein | 43112B | 10413B | 1391 ms | 1321 ms |
handschriftlich | unsicher | 43120B | 10413B | 1393 ms | 1212 ms |
iostream | allgemein | 48632B | 8413B | 10992 ms | 12771 ms |
msgpack | allgemein | 77384B | 8857B | 3563 ms | 14705 ms |
protobuf | allgemein | 2032712B | 10018B | 18125 ms | 20211 ms |
protobuf | Arena | 2032760B | 10018B | 9166 ms | 11378 ms |
Ja | allgemein | 51000B | 10463B | 2114 ms | 1558 ms |
Ich wünsche Ihnen, dass Sie diese Bibliothek nützlich finden. Bitte zögern Sie nicht, Probleme einzureichen, Verbesserungsvorschläge zu machen usw.