是否已将指向第一个成员的指针解释为类本身定义正确?

时间:2019-05-02 13:55:50

标签: c++ c++11 casting void-pointers

我有一些看起来像这样的代码:

template<typename T>
struct memory_block {
    // Very not copiable, this class cannot move
    memory_block(memory_block const&) = delete;
    memory_block(memory_block const&&) = delete;
    memory_block(memory_block&) = delete;
    memory_block(memory_block&&) = delete;
    memory_block& operator=(memory_block const&) = delete;
    memory_block& operator=(memory_block&&) = delete;

    // The only constructor construct the `data` member with args
    template<typename... Args>
    explicit memory_block(Args&&... args) noexcept :
        data{std::forward<Args>(args)...} {}

    T data;
};

template<typename T>
struct special_block : memory_block<T> {
    using memory_block<T>::memory_block;
    std::vector<double> special_data;
};

// There is no other inheritance. The hierarchy ends here.

现在,我必须将这些类型存储到类型已擦除的存储中。我选择了void*的向量作为容器。我将data成员的指针插入向量:

struct NonTrivial { virtual ~NonTrivial() {} };

// exposed to other code
std::vector<void*> vec;

// My code use dynamic memory instead of static
// Data, but it's simpler to show it that way.
static memory_block<int> data0;
static special_block<NonTrivial> data1;

void add_stuff_into_vec() {
    // Add pointer to `data` member to the vector.
    vec.emplace_back(&(data0->data));
    vec.emplace_back(&(data1->data));
}

然后在代码后面,我访问数据:

// Yay everything is fine, I cast the void* to it original type
int* data1 = static_cast<int*>(vec[0]);
NonTrivial* data1 = static_cast<NonTrivial*>(vec[1]);

问题在于,在非平凡的情况下,我想访问special_data

// Pretty sure this cast is valid! (famous last words)
std::vector<double>* special = static_cast<special_block<NonTrivial>*>(
    static_cast<memory_block<NonTrivial>*>(vec[1]) // (1)
);

现在,问题

问题在第(1)行出现:我有一个指向data(类型为NonTrivial)的指针memory_block<NonTrivial>。我知道void*将始终指向memory_block<T>的第一个数据成员。

那么将void*投射到类的第一个成员中是否安全?如果没有,还有另一种方法吗?如果可以简化事情,我可以摆脱继承。

此外,在这种情况下,使用std::aligned_storage也没有问题。如果可以解决问题,我将使用它。

我希望标准布局可以在这种情况下为我提供帮助,但是我的静态断言似乎失败了。

我的静态断言:

static_assert(
    std::is_standard_layout<special_block<NonTrivial>>::value,
    "Not standard layout don't assume anything about the layout"
);

1 个答案:

答案 0 :(得分:15)

只要memory_block<T>是标准布局类型[class.prop]/3memory_block<T>的地址及其第一个成员data的地址就是指针可互换的{{3 }}。在这种情况下,该标准保证您可以reinterpret_cast从指向另一个的指针获取指向另一个的指针。没有标准版式类型时,就没有这种保证。

对于您的特定情况,memory_block<T>将是标准布局,只要T是标准布局。您的special_block永远不会是标准布局,因为它包含一个std::vector(正如@NathanOliver在下面的评论中所指出的那样),它不能保证是标准布局。在您的情况下,由于您只是插入了指向data的{​​{1}}子对象的memory_block<T>成员的special_block<T>成员的指针,因此只要T是标准的,您仍然可以使它起作用-layout,如果您将reinterpret_cast void*返回memory_block<T>*,然后static_cast返回special_block<T>*(假设您确定动态类型完整对象实际上是special_block<T>)。不幸的是,NonTrivial进入图片后,所有投注都被取消,因为NonTrivial具有虚拟方法,因此不是标准布局,这也意味着memory_block<NonTrivial>将不是标准布局…

您可以做的一件事是,例如,只有一个缓冲区为T中的memory_block提供存储,然后在{{1 }}(通过新展示位置)。例如:

T

那样,data将始终是标准版式……