由于delete运算符释放内存,为什么我需要析构函数?

时间:2012-02-03 07:43:16

标签: c++ destructor delete-operator

来自c ++ FAQ:http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.9

  

请记住:delete p有两件事:它调用析构函数并释放内存。

如果delete取消分配内存,那么析构函数需要什么?

9 个答案:

答案 0 :(得分:13)

  

如果删除释放内存,那么需要什么   析构函数在这里?

析构函数的目的是执行在对象之后清理所需的任何逻辑,例如:

  • 在被销毁对象拥有的其他对象上调用delete
  • 正确释放数据库连接等其他资源;文件句柄等

答案 1 :(得分:9)

除了解除分配内存之外,还需要调用析构函数以防其他事情需要完成。

除了非常简单的课程外,通常都有。

关闭文件句柄或关闭数据库连接,删除对象中成员数据指向的其他对象等等。

一个典型的例子是堆栈的实现:

class myStack {
    private:
        int *stackData;
        int topOfStack;

    public:
        void myStack () {
            topOfStack = 0;
            stackData = new int[100];
        }

        void ~myStack () {
            delete [] stackData;
        }

        // Other stuff here like pop(), push() and so on.
}

现在想想如果每次删除一个堆栈时都没有调用析构函数会发生什么。在这种情况下,C ++中没有自动垃圾收集,因此stackData内存会泄漏,最终会耗尽。


这要求析构函数删除其所有资源,从树向下移向基本类型。例如,您可能拥有一个包含数据库连接数组的数据库连接池。这个析构函数将delete每个单独的数据库连接。

单个数据库连接可能会分配很多东西,例如数据缓冲区,缓存,编译的SQL查询等。因此,数据库连接的析构函数也必须delete所有这些。

换句话说,你有类似的东西:

+-------------------------------------+
| DB connection pool                  |
|                                     |
| +-------------------------+---+---+ |
| | Array of DB connections |   |   | |
| +-------------------------+---+---+ |
|                             |   |   |
+-----------------------------|---|---+
                              |   |   +---------+
                              |   +-> | DB Conn |
             +---------+      |       +---------+
             | DB Conn | <----+         /  |  \
             +---------+         buffers   |   queries
               /  |  \                  caches
        buffers   |   queries
               caches

释放数据库连接池的内存不会影响单个数据库连接或它们指向的其他对象的存在。

这就是为什么我提到只有简单的类可以在没有析构函数的情况下离开,而那些类往往会出现在上面那棵树的底部。

类似:

class intWrapper {
    private:
        int value;
    public:
        intWrapper () { value = 0; }
        ~intWrapper() {}
        void setValue (int newval) { value = newval; }
        int getValue (void) { return value; }
}

对于析构函数没有真正的需要,因为你只需要进行内存释放。


底线是newdelete是同一极点的两端。调用new首先分配内存然后调用相关的构造函数代码以使对象处于可操作状态。

然后,当你完成后,delete调用析构函数“拆除”你的对象,回收为该对象分配的内存。

答案 2 :(得分:3)

假设您有一个动态分配内存的类:

class something {
public:
    something() {
        p = new int;
    }

    ~something() {
        delete p;
    }

    int *p;
};

现在让我们动态分配一个something对象:

something *s = new something();

delete s;

现在,如果delete没有调用析构函数,那么s->p永远不会被释放。所以delete必须同时调用析构函数然后释放内存。

答案 3 :(得分:2)

析构函数负责释放除了对象分配的内存之外的资源。例如,如果对象打开了文件句柄,则析构函数可以在其上调用fclose

答案 4 :(得分:1)

它释放 对象所占用的内存。但是,任何已经分配对象(并由该对象拥有)的东西都需要在析构函数中处理。

另外,一般来说......常见问题...通常没有错。

答案 5 :(得分:0)

如果声明一个类正常(不是指针),它会自动调用构造函数并在程序关闭时自动调用析构函数。如果声明为指针,则在使用new初始化时调用构造函数,并且在使用delete调用delete指针之前不会自动调用析构函数

答案 6 :(得分:0)

析构函数用于清除对象构造函数和成员函数可能对程序状态所做的更改。这可以是任何东西 - 从某个全局列表中删除对象,关闭打开的文件,释放已分配的内存,关闭数据库连接等。

答案 7 :(得分:0)

析构函数不会是强制性功能。像C,Java,C#这样的语言没有析构函数。 C ++也可以没有它。

析构函数是C ++提供的特殊工具(与Constructor相同)。当对象被“销毁”时,它被调用。

销毁意味着,对象范围正式完成,对该对象的任何引用都将是非法的。例如:

A* foo ()
{
  static A obj;  // 'A' is some class
  A *p = &obj;
  return p;
}

在上面的代码中,obj是由static类型创建的A数据; foo()会返回对obj的引用,这是正常的,因为尚未调用obj.~A()。假设obj是非静态的。代码将编译,但A*返回的foo()现在指向的内存位置不再是A对象。手段 - &gt;操作不好/非法。

现在,您应该能够区分内存的释放和对象的破坏。两者都紧密耦合,但有一条细线。

还记得可以在多个地方调用析构函数:

int bar ()
{
  A obj;
  ...
  return 0; // obj.~A() called here
  ...
  return 1; // obj.~A() called here
  ...
  return 2; // obj.~A() called here
}

在上面的示例中,obj.~A()只会被调用一次,但可以从显示的任何位置调用它。

在破坏期间,您可能想要做一些有用的事情。假设class A在对象破坏时计算某些结果;它应该打印计算结果。它可以以C样式的方式完成(在每个return语句中放置一些函数)。但是~A()是一个随时可用的一站式设施。

答案 8 :(得分:0)

除了专注于堆上分配的对象的答案(使用 new ;仅使用 delete 解除分配)...不要忘记,如果你将对象放在堆栈上(因此,不使用 new ),其析构函数将被自动调用 ,它将从堆栈中删除(不调用 delete < / em>)当它超出范围时。因此,当对象超出范围时,您将执行一个保证的功能,这是执行对象分配的所有其他资源(各种句柄,套接字......)的理想位置。这个对象在堆上创建的对象 - 如果它们不能超过这个对象的话。这用于RAII idiom