我有一些看起来像这样的代码:
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"
);
答案 0 :(得分:15)
只要memory_block<T>
是标准布局类型[class.prop]/3,memory_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
将始终是标准版式……