在给定正确的静态断言的情况下,`reinterpret_cast` 是否会从派生 -> 内存 -> 基安全?

问题描述 投票:0回答:1

我正在编写 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
可能不安全且不可移植,但我想用良好的静态断言来保护我的代码。我有漏掉吗?

c++ serialization reinterpret-cast memory-layout
1个回答
0
投票

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);

因此,如果您升级,请记住这一点。

© www.soinside.com 2019 - 2024. All rights reserved.