I'm writing C++14 code which needs to deserialize objects as fast as possible.
The serialization is in memory and occurs in the same process as the deserialization.
Consider this:
struct Base
{
Base(const int a) :
a {a}
{
}
int a;
};
struct Derived : public Base
{
Derived(const int a, const int b, const int c) :
Base {a},
b {b},
c {c}
{
}
int b;
int c;
};
Assume all those objects will only contain int members.
What I'd like is for some memory region to contain contiguous memory representations of those objects, for example:
[Base] [Base] [Derived] [Base] [Derived] [Derived]
Let's just pretend you may know if it's Derived or not from the Base::a member.
Is the following code safe given the two static assertions?
#include <iostream>
struct Base
{
Base(const int a) :
a {a}
{
}
int a;
};
struct Derived : public Base
{
Derived(const int a, const int b, const int c) :
Base {a},
b {b},
c {c}
{
}
int b;
int c;
};
static_assert(alignof(Derived) == alignof(Base),
"`Base` and `Derived` share the same alignment");
static_assert(sizeof(Derived) == sizeof(Base) + 2 * sizeof(int),
"Size of `Derived` is expected");
int main()
{
const Derived d {1, 2, 3};
const auto dP = reinterpret_cast<const int *>(&d);
auto& base = reinterpret_cast<const Base&>(*dP);
std::cout << base.a << std::endl;
}
Then the memory region would simply be an std::vector<int>, for example.
I know I'm assuming some compiler-specific stuff here and those two reinterpret_cast are probably unsafe and not portable, but I'd like to protect my code with good static assertions. Am I missing any?
Derivedis not a standard layout class. The layout doesn't have the guarantees you expect. In particular, even ifsizeof(Derived) == sizeof(Base) + sizeof(int[2]), theBasesubobject could be after the members (or inbetween), not that any compiler actually does this.If this assertion passes:
(And an implicit
offsetof(Base, a) == 0, but this is guaranteed becausestd::is_standard_layout_v<Base>)Then you know your pointers will have the correct addresses (and the fact that
(void*) &d.a,(void*) &dand(void*) &(Base&) dwill all be equal)In C++14, this is enough and your
reinterpret_casts will work as-is.In C++17, you would have to launder your pointers:
So keep this in mind if you ever upgrade.