与此处std::aligned_storage
页面的示例类似,假设我有以下内容:
template <typename T, std::size_t N>
class SlotArray {
static_assert(!std::is_array_v<T>);
static_assert(!std::is_void_v<T>);
using StorageType = std::aligned_storage_t<sizeof(T), alignof(T)>;
public:
template<typename ...Args>
T* alloc(Args... args) {
std::size_t index = claimIndex();
return ::new(&slots[index]) T(std::forward<Args>(args)...);
}
void free(T* value) {
static_assert(sizeof(std::ptrdiff_t) <= sizeof(std::size_t));
StorageType* storagePtr = reinterpret_cast<StorageType*>(value);
free(storagePtr - slots);
}
void free(std::size_t index) {
std::destroy_at(std::launder(reinterpret_cast<T*>(&slots[index])));
freeIndex(index);
}
private:
std::size_t claimIndex() {
// Get a unique index, doesn't matter here how (say, bitset + throw if empty)
...
}
void freeIndex(std::size_t index) {
...
}
class alignas(T) Storage {
std::byte buffer[sizeof(T)];
};
StorageType slots[N];
};
这里有UB 吗?
成员函数alloc
是标准的placement-new,free
带有索引参数的成员函数重载是教科书上的std::launder
东西。指针算法定义明确。更难验证的部分是reinterpret_cast
底层存储类型。除了数组的一些极端情况外,有很多迹象表明placement new的指针类型应该是相同的地址。此外,强制转换的结果显然具有相同的对齐方式,并且实际上指向数组中的某个对象。
当然,这一切都假设用户不会传递一些随机数T*
,而只传递从返回的指针alloc
。