【问题标题】:Does a destructor always get called for a delete operator, even when it is overloaded?是否总是为删除运算符调用析构函数,即使它被重载?
【发布时间】:2009-11-16 15:01:16
【问题描述】:

我正在将一些旧代码从 C 移植到 C++。旧代码使用类似对象的语义,并且在某一时刻将对象销毁与释放现在未使用的内存分开,stuff 发生在两者之间:

Object_Destructor(Object *me) { free(me->member1), free(me->member2) }

ObjectManager_FreeObject(ObjectManager *me, Object *obj) { free(obj) }

在 C++ 中使用标准析构函数 (~Object) 并随后调用 delete obj 是否可以实现上述功能?或者,正如我所担心的那样,这样做会调用析构函数两次?

在特定情况下,Objectoperator delete 也会被覆盖。我在其他地方读到的定义(“当使用操作符删除,并且对象有析构函数时,析构函数总是被调用)在被覆盖的操作符情况下是否正确?

【问题讨论】:

    标签: c++ operator-overloading destructor


    【解决方案1】:

    delete operator 用于释放内存,它不会改变析构函数是否被调用。首先调用析构函数,然后才调用delete operator 用于释放内存。

    换句话说,使用 C++ 的析构函数和删除操作符无法实现您所针对的语义。

    Sample:

    #include <iostream>
    #include <new>
    using namespace std;
    
    struct foo {
        ~foo() { cout << "destructor\n"; }
        void operator delete(void* p) { 
            cout << "operator delete (not explicitly calling destructor)\n"; 
            free(p);
            cout << "After delete\n"; 
        }
    };
    
    int main()
    {
        void *pv = malloc(sizeof(foo));
        foo* pf = new (pv) foo; // use placement new
        delete pf;
    }
    

    输出:

    析构函数

    操作符删除(不显式调用析构函数)

    删除后

    【讨论】:

    • 谢谢,但这不是我问的。在 C 代码中看到的功能(与内存删除分开的析构函数)是否可以使用 C++ 析构函数和运算符删除(正常或重载)在 C++ 中实现?还是运算符 delete 总是伴随着对析构函数的调用?
    • 我的回答是肯定的,它总是耦合的,或者换句话说,不,你不能用析构函数和删除操作符来实现这些语义。
    • 它总是耦合的。为什么你需要解耦它呢?我能想到的唯一原因是有不止一种方法来删除一个对象。但在那种假设的情况下,你将无法依靠语言知道当对象超出范围时自动调用什么析构函数.
    • 您有时希望能够删除一个对象并将其内存重新用于新对象(使用新位置),但是在不首先销毁它的情况下释放对象内存是没有好处的(至少 想不出这样的场景)。
    【解决方案2】:

    重载的delete 仍然在它开始执行之前隐式调用析构函数,而不是放置删除(但放置删除不应该直接调用)。 因此,如果您要“删除”对象,请不要提前销毁它,您将调用两次析构函数。但是,如果对象是使用新位置创建的,则需要显式销毁(但在这种情况下,您不会使用 delete 销毁对象)

    【讨论】:

      【解决方案3】:

      在对象的销毁和对象的内存释放之间发生了什么样的事情?如果它与对象无关,那么您应该可以删除析构函数出现的对象。如果确实如此,那么我会非常仔细地检查,因为这听起来是个坏主意。

      如果你必须重现语义,有一个释放所有资源的成员函数,并使用它而不是 destruct 函数。确保可以安全地多次调用该函数,并将其包含在 C++ 析构函数中以确保安全。

      【讨论】:

        【解决方案4】:

        我完全不明白为什么人们说这是不可能的。

        将初始化与构造解耦,将归零 (tm) 与销毁分离实际上非常简单。

        class Clike
        {
        public:
          Clike() : m_usable(true) {}
        
          void clear(); // performs clean up then sets m_usable to false
          ~Clike() { if (m_usable) this->clear(); }
        
        private:
          bool m_usable;
          // real variables
        };
        

        那么你可以这样使用它:

        Clike* c = new Clike();
        
        c->clear(); // performs cleanup
        
        // stuff
        
        delete c;
        

        实际上,由于析构函数永远不应该抛出并且不返回任何东西,所以将cleanupdestruction 分开以使cleanup 操作可能会报告错误并不罕见。特别是对于数据库连接等复杂的野兽......

        虽然这不是“析构函数”,但它确实有效,因此呈现的 C 代码实际上是完全可重现的,无需那些花哨的放置 new 等...

        【讨论】:

          【解决方案5】:

          您可以将销毁与删除分开,但您可能并不想这样做。

          如果您使用new char[]malloc 分配内存,然后调用placement new,那么您可以将销毁(通过直接调用析构函数)与删除(或free)分开。但是,您不再调用类的重载operator delete,而是在字符数组(或free)上调用delete[]

          如果您通过指向您的类的指针(您重载操作符 delete 的那个)调用 delete,那么该类的析构函数将被调用。因此,在没有析构函数的情况下调用 delete,就无法按照您的要求将它们分开。

          【讨论】:

          • 实际上你可以销毁一个对象而不释放内存(通过显式调用dtor p-&gt;~Type)你不能做相反的事情,释放内存而不破坏对象.
          • 好的,你可以显式调用dtor,然后显式调用operator delete。但我不认为这是为该类分配了new 的对象的定义行为。当我说“你不能”时,我的意思是“有效地”。当然,我认为它无效可能是错误的。
          • Steve,如果您显式调用析构函数然后调用 delete,它将导致析构函数运行两次,这是未定义的。如果你调用析构函数,然后在该内存块上调用placement new,然后然后调用delete,你就可以了,因为每个构造对象的析构函数只被调用一次。
          • 好吧,你说得对,我被冲昏了头脑。当提问者说“中间发生了一些事情”时,我认为他的意思是“现有代码中已经有一些东西”,而不是“是否存在我可以做的事情可以使它有效?”。但是我说 C++ 认为这是非法的是错误的——语言没有,只是你在异常安全方面存在问题。如果placement new 抛出异常,那么您就别无选择了,因为您无法重新构造对象,也无法删除它。
          【解决方案6】:

          不,这是不可能的。

          delete 调用析构函数。

          您需要制定某种逻辑来确保 Stuff 以正确的顺序发生。

          【讨论】:

            【解决方案7】:

            看看 std::allocators 的实现。答案是“是的,它们可能是解耦的”。这很容易做到,只是很少见。

            【讨论】:

              【解决方案8】:

              听起来您可能想要placement new。另一方面,听起来您的代码也变得很麻烦。可能是时候进行一些重大的重构了。

              【讨论】:

              • 嗯,为什么投反对票?据我了解,这个问题是关于将内存释放与对象销毁分开,我的回答与此相关。
              • 我的猜测是你被否决了,因为放置 new 与分离构造和内存分配有关,问题是关于分离破坏和内存_de_allocation。正如我在对已接受答案的评论中所说,您可以在没有解除分配的情况下进行销毁,但您不能在没有销毁的情况下进行解除分配(除非您选择使用char 缓冲区来颠覆类型系统)。
              • 如你所说,你必须颠覆类型系统。它确实有效。顺便说一句,我的理解是,当您使用新放置时,通常您会分别进行销毁和释放。查看我的答案中的链接。
              【解决方案9】:

              析构函数与 delete 耦合,你不能调用它两次,因为你不能明确地调用析构函数(或者至少它是非常不寻常和不常见的,我从未见过它)。

              但是,只需将 object_destructor() 设为成员函数并显式调用它(通常,确保被调用两次的安全性是一种很好的方式。但是,在你的情况下,调用两次是可以的,因为调用 free() 时使用NULL 指针是合法的,所以 object_destructor 的替代版本只是为了强调它是如何完成的。

              CLASS A {
                 object_destructor() { free(this->member1); free(this->member2); }
              
                 alternate_version_of_object_destructor() {  
                         if (this->member1) { free(this->member1); this->member1= NULL; } 
                         if (this->member2) { free(this->member2); this->member2= NULL; } }
              
                  ~A() { /* do nothing or just call this->object_destructor() for safety */ }
              }
              
              
              foo() {
                  A *pa= new A;
              
              
                  pa->object_destructor();  /* member cleanup */
                  delete pa;                /* free object memory */
              } 
              

              【讨论】:

                猜你喜欢
                • 2017-04-04
                • 2016-07-11
                • 1970-01-01
                • 2014-06-19
                • 2014-03-13
                • 2017-03-12
                • 2011-10-11
                • 2011-11-05
                • 2013-02-06
                相关资源
                最近更新 更多