c++ - 类是否应该具有双重破坏的弹性?

标签 c++ destructor

我遇到这样一种情况,其中并发访问的容器的分配器需要同时调用 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/

相关文章:

在对象销毁之前清理 Java 资源

使用类模板的 C++ 循环依赖 - 如何重构?

c++ - MAKE 的 GCcflags从哪里传递?

c - 调用 Seg Fault 以销毁 malloced 结构

c++ - 在静态对象的析构函数中使用 cout

c# - 为什么 Finalize/Destructor 示例在 .NET Core 中不起作用?

c++ - 为什么不能显式调用构造函数而析构函数可以?

c++ - 令人费解的编译器优化结果

C++: double 、精度、虚拟机和 GCC

c++ - 尝试翻转 std::bitset 中的位顺序