C++ Serialization & Reflection
Cista++ is a simple, open source (MIT license) C++17 compatible
way of
(de-)serializing C++ data structures.
Single header. No macros. No source code generation.
- Raw performance - use your native structs.
Supports modification/resizing of deserialized data! - Supports complex and cyclic data structures including cyclic references, recursive data structures, etc.
- Save 50% memory: serialize directly to the filesystem if needed, no intermediate buffer required.
- Fuzzing-checked though continuous fuzzing using LLVMs LibFuzzer.
- Comes with a serializable high-performance hash map and hash set implementation based on Google's Swiss Table technique.
- Reduce boilerplate code: automatic derivation of hash and equality functions.
- Optional: built-in automatic data structure versioning through recursive type hashing.
- Optional: check sum to prevent deserialization of corrupt data.
- Compatible with Clang, GCC, and MSVC
namespace data = cista::raw;
struct my_struct { // Define your struct.
int a_{0};
struct inner {
data::string b_;
} j;
};
std::vector<unsigned char> buf;
{ // Serialize.
my_struct obj{1, {data::string{"test"}}};
buf = cista::serialize(obj);
}
// Deserialize.
auto deserialized = cista::deserialize<my_struct>(buf);
assert(deserialized->j.b_ == data::string{"test"});
namespace data = cista::offset;
constexpr auto const MODE = // opt. versioning + check sum
cista::mode::WITH_VERSION | cista::mode::WITH_INTEGRITY;
struct pos { int x, y; };
using pos_map = // Automatic deduction of hash & equality
data::hash_map<data::vector<pos>,
data::hash_set<data::string>>;
{ // Serialize.
auto positions =
pos_map{{{{1, 2}, {3, 4}}, {"hello", "cista"}},
{{{5, 6}, {7, 8}}, {"hello", "world"}}};
cista::buf mmap{cista::mmap{"data"}};
cista::serialize<MODE>(mmap, positions);
}
// Deserialize.
auto b = cista::mmap("data", cista::mmap::protection::READ);
auto positions = cista::deserialize<pos_map, MODE>(b);
Use Cases
Reader and writer should run on the same architecture (e.g. 64 bit little endian). Examples:
- Asset loading for all kinds of applications (i.e. game assets, GIS data, large graphs, etc.)
- Transferring data over network
- Shared memory applications
Currently, only C++17 software can read/write data. It is possible to generate accessors for other programming languages, too.
Benchmarks
Have a look at the benchmark repository for more details.
| Library | Serialize | Deserialize | Fast Deserialize | Traverse | Deserialize & Traverse | Size |
|---|---|---|---|---|---|---|
| Cap’n Proto | 105 ms | 0.002 ms | 0.0 ms | 356 ms | 353 ms | 50.5M |
| cereal | 239 ms | 197.000 ms | - | 125 ms | 322 ms | 37.8M |
Cista++ offset |
72 ms | 0.053 ms | 0.0 ms | 132 ms | 132 ms | 25.3M |
Cista++ raw |
3555 ms | 68.900 ms | 21.5 ms | 112 ms | 133 ms | 176.4M |
| Flatbuffers | 2349 ms | 15.400 ms | 0.0 ms | 136 ms | 133 ms | 378.0M |
Human Readable to String
struct a {
CISTA_PRINTABLE(a)
int i_ = 1;
int j_ = 2;
double d_ = 100.0;
std::string s_ = "hello";
};
int main() {
a instance;
std::cout << instance;
// "{1, 2, 100, hello}"
}
Modify Struct as Tuple
struct a {
int i_ = 1;
int j_ = 2;
double d_ = 100.0;
std::string s_ = "hello";
};
int main() {
using cista::to_tuple;
using std::get;
a i;
get<0>(to_tuple(i)) = 5;
get<1>(to_tuple(i)) = 7;
get<2>(to_tuple(i)) = 2.0;
get<3>(to_tuple(i)) = "yeah";
}
The Cista++ Serialization is based on a powerful reflection concept made possible by the C++17 structured bindings feature.
This page presents some other utilities enabled by this technique.
Comparable: Generate Operators
struct a {
CISTA_COMPARABLE()
int i_ = 1;
int j_ = 2;
double d_ = 100.0;
std::string s_ = "hello";
};
int main() {
a inst1, inst2;
CHECK(inst1 == inst2);
inst1.j_ = 1;
CHECK(!(inst1 == inst2));
CHECK(inst1 != inst2);
CHECK(inst1 <= inst2);
}
Iterate Each Field
struct a {
int i_ = 1;
int j_ = 2;
double d_ = 100.0;
std::string s_ = "hello";
};
int main() {
a i;
cista::for_each_field(
i, [](auto&& m) { m = {}; });
CHECK(i.i_ == 0);
CHECK(i.j_ == 0);
CHECK(i.d_ == 0.0);
CHECK(i.s_ == "");
}
Generate SQL Statements
struct row {
sql_col<
int, name("id"),
primary, not_null> user_id;
sql_col<
std::string, name("first"),
not_null> first_name;
sql_col<
std::string, name("last"),
not_null> last_name;
} r;
int main() {
std::cout <<
create_table_statement(r);
// CREATE TABLE (
// id INT PRIMARY NOT NULL,
// first TEXT NOT NULL,
// last TEXT NOT NULL
// );
}