假设我有一个MyStack
类暴露:
class MyStack {
public:
template <typename T>
T* Push() {
Reserve(sizeof(T)); // Make sure that the buffer can hold an additional sizeof(T) bytes , realloc if needed
auto prev= _top;
_top += sizeof(T);
new (prev) T();
return reinterpret_cast<T*>(prev);
}
template <typename T>
T* Pop() {
_top -= sizeof(T);
return return reinterpret_cast<T*>(_top);
}
bool Empty() const {
return _bottom == _top;
}
private:
char* _bottom;
char* _top;
};
// Assumes all stack elements have the same type
template <typename T>
void ClearStack(MyStack& stack) {
while (!stack.Empty()) {
stack.template Pop<T>()->~T();
}
}
这里有一个隐藏的错误。在T
中构造MyStack::Push()
可能会导致堆栈缓冲区处于未定义状态(分配的空间将包含垃圾)。稍后,当调用ClearStack
时,它将尝试将垃圾重新解释为T
并调用其析构函数,这可能会导致访问冲突。
有没有办法通过修改MyStack::Push()
来修复此错误? (限制是因为这是一个外部代码,我们更喜欢进行最小的更改,因此更新库相对容易)
我考虑过将MyStack::Push
更改为:
T* Push() {
auto prev = _top;
T t();
Reserve(sizeof(T));
_top += sizeof(T);
reinterpret_cast<T*>(prev) = std::move(t);
return prev;
}
但它看起来很糟糕,我甚至不确定它不会调用任何UB(并强制T
有一个移动构造函数)
这里是否有更好的解决方案来防止抛出构造函数? (最好是MyStack::Push()
)内的小变化
答案 0 :(得分:3)
这里的问题实际上是你的设计错了。你正在创建一个类似于std::vector
的类型,但它没有“容量”的实际概念。因此,当它Reserve
内存时,它真正期望在此过程完成后_top
将指向已分配存储的末尾。因此,如果没有,则类型处于无效状态。
这意味着,如果发生异常,您必须撤消对Reserve
的调用:重新分配旧的存储大小并将该存储中的内容移回 1 。更像vector
的实现有3个指针:指向开始的指针,指向第一个未使用的内存字节的指针,以及指向已分配存储结束的指针。这样,如果您Reserve
但是获得例外,那么您只需要一些额外的存储空间。
1 :仅供参考:你似乎最想做的事情是行不通的。或者至少,不是大多数用户定义的C ++类型。您的Reserve
调用分配新存储并在其中执行memcpy
并且从不在这些对象上调用析构函数(因为您不知道它们是什么类型)的可能性很大。嗯,这对于memcpy
是有效行为的对象来说是合法的。即,TriviallyCopyable类型。然而,你的Push
函数无法防范非TriviallyCopyable类型。
更不用说如果有人有指向旧对象的指针,那么每个Push
调用都会使该指针无效。因为你不记得任何物体的类型,所以没有办法重建它们。
答案 1 :(得分:1)
这段代码怎么样:
template <typename T>
T* Push() {
Reserve(sizeof(T));
auto prev= _top;
_top += sizeof(T);
try {
new (prev) T();
return reinterpret_cast<T*>(prev);
}
catch (...) {
Unreserve(sizeof(T)); //release the memory, optional?
_top = prev;
throw;
}
}
答案 2 :(得分:0)
您可以使用三指针实现:
begin
指向第一个元素。end
指向最后一个元素之后的一个 reserved
指向保留空间之后的一个元素。
begin=end=reserved(=nullptr)
表示未分配的容器。
begin+1=end=reserved
表示带有一个元素的已填充容器。begin+1=end;begin+4=reserved
表示具有一个元素的容器,另外还有2个空格。然后你的Push方法看起来像:
template <typename T>
T* Push() {
if(end==reserved)
//relocate, ensure that begin<=end<reserved
new (end) T();
end+=sizeof(T);
return reinterpret_cast<T*>(end-1);
}
答案 3 :(得分:-1)
如果您需要堆栈实施,可以尝试使用std::stack
。
如果你想自己实现它,那么考虑让整个类模板化 - 这将消除对reinterpreter_cast
的需要。