Semelhante ao exemplo da std::aligned_storage
página aqui , imagine que eu tenho o seguinte:
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];
};
Tem algum UB aqui?
A alloc
função membro é o posicionamento-novo padrão e a free
sobrecarga da função membro com um parâmetro de índice é std::launder
coisa de livro didático. A aritmética de ponteiro é bem definida. A parte que é mais difícil de verificar é o reinterpret_cast
tipo de armazenamento subjacente. Fora de alguns casos extremos com matrizes, há muitas indicações de que o tipo de ponteiro do posicionamento novo deve ser o mesmo endereço. Além disso, o resultado da conversão claramente tem o mesmo alinhamento e, na verdade, aponta para algo na matriz.
Certamente, tudo isso pressupõe que o usuário não passe nenhum ponteiro aleatório T*
, mas apenas ponteiros retornados de alloc
.