【发布时间】:2022-01-14 22:27:33
【问题描述】:
我正在编写一个自定义 Stack 容器,它将其元素存储在一个固定大小的数组中:
template<typename T, uint32 TCapacity>
class Stack {
// Member functions omitted
T mData[TCapacity];
uint32 mSize;
};
当一个元素从堆栈中弹出时,我可以减小它的大小。但是,我认为如果项目从堆栈中弹出,您还希望在对象上调用析构函数。
所以,我可以在弹出对象时手动调用析构函数,如下所示:
void Pop() {
assert(mSize > 0);
mSize--;
mData[mSize].~T();
}
但是,当 Stack 对象本身被破坏时,这不会导致为 mData 中的每个对象再次调用析构函数,从而有效地“双重破坏”某些元素吗?双重破坏所有类型可能不安全,所以这似乎不是一个好主意。
我想另一种方法是构造一个新对象来覆盖以前的对象,但这似乎有点效率低下:
void Pop() {
assert(mSize > 0);
mSize--;
mData[mSize] = T();
}
我唯一能想到的另一件事是让数据数组只是一个字节数组(无符号字符),然后处理在该原始内存中构造/破坏对象的额外复杂性。哪个看起来不太理想,但也许才是真正的解决方案?
有没有人知道解决这个问题的好方法?我想像 std::vector 这样的内置容器也必须处理这个问题(当然,在这种情况下,数据内存是从堆中分配的)。
【问题讨论】:
-
您缺少的是placement new。它将对象构造成预先存在的存储而不是分配。像
std::vector<T>这样的容器实际上并不拥有T[]数组。他们使用分配器来获取存储空间,在其上执行放置new以插入元素并显式调用析构函数,就像您建议稍后删除它们一样。您可以使用std::allocator<T>轻松获取和释放存储空间。 -
好的,有道理 - 所以这里避免“双重删除”的关键是没有 T 类型的数组。这会阻止每个元素的析构函数在超出范围时被调用. (到目前为止)似乎可行的方法是定义一个固定大小的字节数组
unsigned char mData[TCapacity * sizeof(T)];,然后当我需要将其视为 T 数组时,我可以使用 reinterpret_cast:reinterpret_cast<T*>(mData)[index] = instanceOfT。 -
@kromenak 基本上,是的。但是,您应该使用
std::aligned_storage而不是原始数组。 -
@RemyLebeau 感谢您的注意 - 我忽略了对齐问题,但我绝对可以看到这是一个问题。内存缓冲区应针对特定类型 T 对齐。
标签: c++ memory stack destructor