我正在编写 C++14 代码,需要尽快反序列化对象。
序列化在内存中,并且与反序列化发生在同一进程中。
考虑一下:
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;
};
假设所有这些对象仅包含
int
成员。
我想要的是一些内存区域包含这些对象的连续内存表示,例如:
[Base] [Base] [Derived] [Base] [Derived] [Derived]
我们假设您可能知道这是否是
Derived
或不是来自 Base::a
成员。
考虑到两个静态断言,以下代码安全吗?
#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;
}
那么内存区域就只是一个
std::vector<int>
,例如。
我知道我在这里假设一些特定于编译器的东西,而这两个
reinterpret_cast
可能不安全且不可移植,但我想用良好的静态断言来保护我的代码。我有漏掉吗?
Derived
不是标准布局类。该布局没有您期望的保证。特别是,即使 sizeof(Derived) == sizeof(Base) + sizeof(int[2])
,Base
子对象也可能位于 之后 成员(或中间),并不是任何编译器实际上都会这样做。
如果此断言通过:
static_assert(offsetof(Derived, Base::a) == 0, "Base::a not at beginning of Derived");
(还有一个隐含的
offsetof(Base, a) == 0
,但这是有保证的,因为 std::is_standard_layout_v<Base>
)
然后您就知道您的指针将具有正确的地址(并且事实上
(void*) &d.a
、(void*) &d
和 (void*) &(Base&) d
都将相等)
在 C++14 中,这就足够了,您的
reinterpret_cast
将按原样工作。
在 C++17 中,你必须清洗你的指针:
const auto *dP = std::launder(reinterpret_cast<const int *>(&d)); // Points to d.Base::a
auto& base = reinterpret_cast<const Base&>(*dP); // pointer-interconvertible with first member of standard layout class
// Or
const auto *dP = reinterpret_cast<const int *>(&d); // Points to d, not d.a, but has the same address
auto& base = *std::launder(reinterpret_cast<const Base *>(dP));
// Or
const auto *dP = &d.a;
auto& base = reinterpret_cast<const Base&>(*dP);
因此,如果您升级,请记住这一点。