C++ 如何以异常安全的方式使用placement new?
假设我有一个C++ 如何以异常安全的方式使用placement new?,c++,new-operator,C++,New Operator,假设我有一个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
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();
}
}
但是它看起来很糟糕,我甚至不确定它是否调用了任何UB(并且还强制t
使用move构造函数)
这里有更好的解决方案来防止抛出构造函数吗?(最好是在MyStack::Push()内部进行一个小的更改)这里的问题实际上是您的设计是错误的。您正在创建的类型的行为有点像
std::vector
,但它没有实际的“容量”概念。因此,当它保留
内存时,它真诚地希望\u top
在该过程完成后将指向分配的存储的末尾。因此,如果没有,则类型处于无效状态
这意味着,在发生异常的情况下,您必须撤消对Reserve
:重新分配旧存储大小并将该存储中的内容移回1的调用。更类似于向量的实现有3个指针:一个指向开始的指针,一个指向内存中第一个未使用字节的指针,以及一个指向已分配存储的结束的指针。这样,如果您保留了,但得到了一个异常,那么您只是得到了一些额外的存储空间
1:仅供参考:你似乎想做的事情很可能不会奏效。或者至少不是大多数用户定义的C++类型。很可能您的Reserve
调用会分配新存储,并对其执行memcpy
操作,并且从不对这些对象调用析构函数(因为您不知道它们是什么类型)。嗯,这只适用于memcpy
是有效操作的对象。也就是说,平凡的可复制类型。然而,您的Push
函数没有任何东西可以防止非平凡的可复制类型
更不用说,如果任何人有一个指向旧对象的指针,那么每次Push
调用都会使该指针无效。而且,由于您不记得任何对象的类型,因此无法重新组合它们。这里的问题实际上是您的设计是错误的。您正在创建的类型的行为有点像std::vector
,但它没有实际的“容量”概念。因此,当它保留
内存时,它真诚地希望\u top
在该过程完成后将指向分配的存储的末尾。因此,如果没有,则类型处于无效状态
这意味着,在发生异常的情况下,您必须撤消对Reserve
:重新分配旧存储大小并将该存储中的内容移回1的调用。更类似于向量的实现有3个指针:一个指向开始的指针,一个指向内存中第一个未使用字节的指针,以及一个指向已分配存储的结束的指针。这样,如果您保留了,但得到了一个异常,那么您只是得到了一些额外的存储空间
1:仅供参考:你似乎想做的事情很可能不会奏效。或者至少不是大多数用户定义的C++类型。很可能您的Reserve
调用会分配新存储,并对其执行memcpy
操作,并且从不对这些对象调用析构函数(因为您不知道它们是什么类型)。嗯,这只适用于memcpy
是有效操作的对象。也就是说,平凡的可复制类型。然而,您的Push
函数没有任何东西可以防止非平凡的可复制类型
更不用说,如果任何人有一个指向旧对象的指针,那么每次Push
调用都会使该指针无效。由于您不记得任何对象的类型,因此无法重新组合它们。这段代码如何:
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;
}
}
模板
T*Push(){
储量(T);
自动上一个=_顶部;
_top+=尺寸f(T);
试一试{
新(上)T();
返回重新解释(上一个);
}
捕获(…){
Unreserve(sizeof(T));//释放内存,可选吗?
_顶部=上一个;
投掷;
}
}
这个代码怎么样:
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;
}
}
模板
T*Push(){
储量(T);
自动上一个=_顶部;
_top+=尺寸f(T);
试一试{
新(上)T();
返回重新解释(上一个);
}
捕获(…){
Unreserve(sizeof(T));//释放内存,可选吗?
_顶部=上一个;
投掷;
}
}
您可以使用三指针实现:
begin
指向第一个元素
end
指向最后一个元素后面的一个
reserved
指向超过保留空间的一个元素
begin=end=reserved(=nullptr)
表示未分配的容器
begin+1=end=reserved
表示用一个元素填充的容器
begin+1=结束;begin+4=保留
表示包含一个元素的容器和可容纳两个以上元素的空间
然后,您的推送方法如下所示:
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);
}
模板
T*Push(){
如果(结束==保留)
//重新定位,确保begin可以使用三指针实现:
begin
指向第一个元素
end
指向最后一个元素后面的一个
reserved
指向超过保留空间的一个元素
begin=end=reserved(=nullptr)
表示未分配的容器
begin+1=end=reserved
表示用一个元素填充的容器
begin+1=end;begin+4=reserved
表示包含一个元素和两个以上空间的容器
然后,您的推送方法如下所示:
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);
}
模板
T*Push(){
如果(结束==保留)
//重新定位,确保开始如果需要堆栈实现,可以尝试使用
如果您想自己实现它,那么考虑将整个类模板化——这样就消除了对<代码>的需要。