是否将指向第一个成员的指针解释为类本身定义良好?
Posted
技术标签:
【中文标题】是否将指向第一个成员的指针解释为类本身定义良好?【英文标题】:Is interpreting a pointer to first member as the class itself well defined? 【发布时间】: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 :
datastd::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
的访问者模式来为您处理类型擦除和访问。
【参考方案1】:
只要memory_block<T>
是标准布局类型[class.prop]/3,memory_block<T>
的地址和它的第一个成员data
的地址就是指针可互转换的[basic.compound]/4.3。如果是这种情况,标准保证您可以reinterpret_cast
从指向另一个的指针中获取指向另一个的指针。只要您没有标准布局类型,就没有这样的保证。
对于您的特定情况,memory_block<T>
将是标准布局,只要 T
是标准布局。您的 special_block
永远不会是标准布局,因为它包含 std::vector
(@NathanOliver 在下面的评论中也指出),这不能保证是标准布局。在您的情况下,由于您只需插入指向您的special_block<T>
的memory_block<T>
子对象的data
成员的指针,因此只要T
是标准布局,您仍然可以使其工作reinterpret_cast
您的void*
回到memory_block<T>*
然后static_cast
到special_block<T>*
(假设你确定完整对象的动态类型实际上是special_block<T>
)。不幸的是,一旦NonTrivial
进入图片,所有的赌注都被取消了,因为NonTrivial
有一个虚拟方法,因此不是标准布局,这也意味着memory_block<NonTrivial>
不会是标准布局......
您可以做的一件事是,例如,只有一个缓冲区来为您的 memory_block
中的 T
提供存储空间,然后通过放置 new 在 data
的存储空间中构造实际的 T
。例如:
#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>
将始终是标准布局……
【讨论】:
我做了一些测试,似乎memory_block<T>
可以,但special_block<T>
不行。如果我之前将指针投射到memory_block<T>
,我还可以吗?
这是否意味着您必须先static_cast<NonTrivial*>(vec[1])
然后再reinterpret_cast<memory_block<NonTrivial>*>
?
@GuillaumeRacicot memory_block<T>
仅当 T
为标准布局时才为标准布局。由于NonTrivial
不是标准布局(它具有虚函数),因此不允许从NonTrivial*
转换为memory_block<NonTrivial>*
。从memory_block<NonTrivial>*
到special_block<NonTrivial>*
与问题无关。或者至少我不明白怎么做。
可能还想指出special_block
从来都不是标准布局,因为它包含vector
。
@NathanOliver 而且因为它本身和它的基类都有非静态成员。以上是关于是否将指向第一个成员的指针解释为类本身定义良好?的主要内容,如果未能解决你的问题,请参考以下文章
C ++ typedef函数定义为类成员,以后用于函数指针?