【发布时间】:2018-09-25 06:17:32
【问题描述】:
让S 是一个结构类型,它包含一个字符数组data,它具有最大对齐和固定大小。这个想法是S 能够存储T 类型的任何对象,其大小不超过限制,并且可以简单地复制构造和简单地破坏。
static constexpr std::size_t MaxSize = 16;
struct S {
alignas(alignof(std::max_align_t)) char data[MaxSize];
};
Placement-new 用于将T 类型的对象构造到新的S 对象的字符数组中。然后这个对象被复制任意次数,包括被返回和按值传递。
template <typename T>
S wrap(T t) {
static_assert(sizeof(T) <= MaxSize, "");
static_assert(std::is_trivially_copy_constructible_v<T>, "");
static_assert(std::is_trivially_destructible_v<T>, "");
S s;
new(reinterpret_cast<T *>(s.data)) T(t);
return s;
}
稍后给出这个S值的副本,reinterpret_cast用于从指向字符数组开头的指针获取T*,然后以某种方式访问T对象。 T 类型与创建值时的类型相同。
void access(S s) {
T *t = reinterpret_cast<T *>(s.data);
t->print();
}
我想知道此方案中是否涉及任何未定义的行为以及如何解决。例如,我担心:
- “重用对象存储”是否存在问题,即
std::launder旨在解决的问题?我不确定在那里构造T的实例后以字符数组的形式访问data是否有效。在访问值的地方我需要std::launder,为什么? - 在生成的
S的复制构造函数中是否存在问题,它复制了data中的所有字节,因为某些字节可能尚未初始化?我担心sizeof(T)之外的字节以及T对象中可能未初始化的字节(例如填充)。
我的用例是实现一个非常轻量级的多态函数包装器,它能够与满足我为T 列出的那些要求的任何可调用对象一起使用。
【问题讨论】:
-
我担心最大的问题是你没有提到的问题,我不知道如何解决:在重新存储之后,复制构造函数对底层字节的访问- 用于其他对象。而且你不能在那里插入
std::launder。 -
@hvd:存储如何被其他对象重用?我创建
S对象的唯一方法是通过create()。我最多可以将这些新的S值分配给现有的S值,但这只是复制字节。 -
您正在重用
new(reinterpret_cast<T *>(s.data)) T(t);中的存储空间。之后,您可以在return s;中直接、隐式地访问存储。我可能错了,但我认为允许编译器看到由placement-new创建的对象永远不会被访问并优化它。 -
@hvd:啊。我想构造一个本地
T对象然后将memcpy构造成S会解决这个问题? -
好点,应该可以。
标签: c++ language-lawyer undefined-behavior placement-new