我遇到这样一种情况,其中并发访问的容器的分配器需要同时调用 placement new 和析构函数。
template< class U > void destroy(U* p){
p->~U();
}
事实上,我最终可以反复调用破坏。 这让我开始思考这样的事情是否应该可行。
std::vector<int>* p = (std::vector<int>*)malloc(sizeof(std::vector<int>));
::new (*p) std::vector<int>(30);
(*p)[10] = 5;
p->~std::vector<int>();
p->~std::vector<int>();
free(p);
我认为只要 std::vector
的销毁将数据指针设置为 null 或将大小设置为零,并且再次调用时没有双重释放,这就会起作用。
那么,是否应该将意外(或良性)双重破坏等同于一次破坏?
换句话说,销毁应该是幂等操作吗?
(为简单起见,请注意此示例中的析构函数不是虚拟的)
我发现这个问题类似于如何允许稍后销毁移动的类的问题。
一些答案将运行时成本作为反对支持双重销毁的理由。 有成本,但它们类似于移动物体的成本。 换句话说,如果移动成本低,则允许双重销毁成本低(如果 DD 由于其他原因(例如标准)首先不是 UB)。
具体来说:
class dummyvector{
int* ptr;
public:
dummyvector() : ptr(new int[10]){}
dummyvector(dummyvector const& other) = delete; // for simplicity
dummyvector(dummyvector&& other) : ptr(other.ptr){
other.ptr = nullptr; // this makes the moved object unusable (e.g. set) but still destructable
}
dummyvector& operator=(dummyvector const&) = delete; // for simplicity
void set(int val){for(int i = 0; i != 10; ++i) ptr[i]=val;}
int sum(){int ret = 0; for(int i = 0; i != 10; ++i) ret += ptr[i]; return ret;}
~dummyvector(){
delete[] ptr;
ptr = nullptr; // this is the only extra cost for allowing double free (if it was not UB)
// ^^ this line commented would be fine in general but not for double free (not even in principle)
}
};
最佳答案
鉴于销毁意味着对象生命周期的结束,处理双重销毁需要付出代价(使内存处于可重新销毁状态的额外工作)以获得格式良好的代码永远不会遇到的好处。
说双重销毁没问题等同于说释放后使用没问题;当然,在特定情况下允许它的分配器可能会使有缺陷的代码继续工作,但这并不意味着它是一个值得保留在分配器中的功能,如果它会阻止分配器在非病态情况下有效和正确地工作。
如果您曾经处于可能发生双重破坏的位置,那么您可能处于可能存在破坏后使用的位置,现在您必须让类中的每个操作都检查并“处理”(不管是什么意思)在被破坏的实例上被调用的可能性。您已经损害了正常操作以允许滥用,这是一条糟糕的开始之路。
简短版本:不防止双重破坏;在你被双重破坏的那一刻,有问题的代码无论如何都进入了未定义的行为领域,处理它不是你的工作。编写一开始就不会做糟糕事情的代码。
关于c++ - 类是否应该具有双重破坏的弹性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48533939/