将指向第一个成员的指针解释为 class 本身是否定义明确?
Is interpreting a pointer to first member as the class itself well defined?
我有一些代码如下所示:
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*
转换为 class 的第一个成员到 class 安全吗?如果没有,还有另一种方法吗?如果能让事情简单点,我可以去掉继承。
此外,我在这种情况下使用 std::aligned_storage
没有问题。能解决问题我就用
我希望标准布局能在这种情况下帮助我,但我的静态断言似乎失败了。
我的静态断言:
static_assert(
std::is_standard_layout<special_block<NonTrivial>>::value,
"Not standard layout don't assume anything about the layout"
);
只要memory_block<T>
是标准排版类型[class.prop]/3, the address of a memory_block<T>
and the address of its first member data
are pointer interconvertible [basic.compound]/4.3。如果是这种情况,标准保证您可以 reinterpret_cast
从指向另一个的指针获取指向一个的指针。一旦您没有标准布局类型,就没有这样的保证。
对于您的特定情况,只要 T
是标准布局,memory_block<T>
就是标准布局。你的 special_block
永远不会是标准布局,因为它包含一个 std::vector
(@NathanOliver 在他下面的评论中也指出),这不能保证是标准布局。在你的例子中,因为你只是插入一个指向 special_block<T>
的 memory_block<T>
子对象的 data
成员的指针,只要 T
是标准的,你仍然可以使它工作-layout if you reinterpret_cast
your void*
back to memory_block<T>*
and then static_cast
that to special_block<T>*
(assuming you know sure that the dynamic type of the complete object 实际上是 special_block<T>
)。不幸的是,一旦 NonTrivial
进入画面,所有赌注都会被取消,因为 NonTrivial
有一个虚拟方法,因此不是标准布局,这也意味着 memory_block<NonTrivial>
将不是标准布局…
你可以做的一件事是,例如,只有一个缓冲区来为你的 memory_block
中的 T
提供存储,然后在 T
的存储中构造实际的 T
=13=] 通过放置新的。例如:
#include <utility>
#include <new>
template <typename T>
struct memory_block
{
alignas(T) char data[sizeof(T)];
template <typename... Args>
explicit memory_block(Args&&... args) noexcept(noexcept(new (data) T(std::forward<Args>(args)...)))
{
new (data) T(std::forward<Args>(args)...);
}
~memory_block()
{
std::launder(reinterpret_cast<T*>(data))->~T();
}
…
};
这样 memory_block<T>
将始终是标准布局...
我有一些代码如下所示:
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*
转换为 class 的第一个成员到 class 安全吗?如果没有,还有另一种方法吗?如果能让事情简单点,我可以去掉继承。
此外,我在这种情况下使用 std::aligned_storage
没有问题。能解决问题我就用
我希望标准布局能在这种情况下帮助我,但我的静态断言似乎失败了。
我的静态断言:
static_assert(
std::is_standard_layout<special_block<NonTrivial>>::value,
"Not standard layout don't assume anything about the layout"
);
只要memory_block<T>
是标准排版类型[class.prop]/3, the address of a memory_block<T>
and the address of its first member data
are pointer interconvertible [basic.compound]/4.3。如果是这种情况,标准保证您可以 reinterpret_cast
从指向另一个的指针获取指向一个的指针。一旦您没有标准布局类型,就没有这样的保证。
对于您的特定情况,只要 T
是标准布局,memory_block<T>
就是标准布局。你的 special_block
永远不会是标准布局,因为它包含一个 std::vector
(@NathanOliver 在他下面的评论中也指出),这不能保证是标准布局。在你的例子中,因为你只是插入一个指向 special_block<T>
的 memory_block<T>
子对象的 data
成员的指针,只要 T
是标准的,你仍然可以使它工作-layout if you reinterpret_cast
your void*
back to memory_block<T>*
and then static_cast
that to special_block<T>*
(assuming you know sure that the dynamic type of the complete object 实际上是 special_block<T>
)。不幸的是,一旦 NonTrivial
进入画面,所有赌注都会被取消,因为 NonTrivial
有一个虚拟方法,因此不是标准布局,这也意味着 memory_block<NonTrivial>
将不是标准布局…
你可以做的一件事是,例如,只有一个缓冲区来为你的 memory_block
中的 T
提供存储,然后在 T
的存储中构造实际的 T
=13=] 通过放置新的。例如:
#include <utility>
#include <new>
template <typename T>
struct memory_block
{
alignas(T) char data[sizeof(T)];
template <typename... Args>
explicit memory_block(Args&&... args) noexcept(noexcept(new (data) T(std::forward<Args>(args)...)))
{
new (data) T(std::forward<Args>(args)...);
}
~memory_block()
{
std::launder(reinterpret_cast<T*>(data))->~T();
}
…
};
这样 memory_block<T>
将始终是标准布局...