【发布时间】:2019-09-21 01:42:30
【问题描述】:
我有一些看起来像这样的代码:
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"
);
【问题讨论】:
-
从技术上讲,我相信编译器会决定将成员变量放在哪里以及如何对齐它们。我不确定您是否可以认为这是一种保证。您可以添加一个
static_assert来检查sizeof(memory_block<T>) == sizeof(T),这可以为您提供保证:-) -
@Neijwiert 是和否。标准提供了一些保证。
-
对于标准布局类型,第一个成员是类的地址。在那之后,我们真的没有其他保证,除了成员按照声明的顺序排列在内存中,但是它们之间可以/将会有填充。
-
@FrançoisAndrieux 类型本身不是多态的,只有一个成员。有时。
-
如果您可以升级到 C++17,您可以使用
std::any和带有std::visit的访问者模式来为您处理类型擦除和访问。
标签: c++ c++11 casting void-pointers