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

发布于 2025-01-01 14:31:30 字数 282 浏览 2 评论 0原文

来自 C++ 常见问题解答: http://www.parashift.com/ c++-faq-lite/dtors.html#faq-11.9

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

如果delete释放了内存,那么这里还需要析构函数吗?

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

Remember: delete p does two things: it calls the destructor and it deallocates the memory.

If delete deallocates the memory, then what's the need of the destructor here?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

花开浅夏 2025-01-08 14:31:31

除了关注在堆上分配的对象(使用 new ;仅使用删除来释放)的答案之外......不要忘记,如果您放置对象在堆栈上(因此,不使用new),它的析构函数将被自动调用,并且将从堆栈中删除(不调用delete ) 当它超出范围时。因此,当对象超出范围时,您有一个保证执行的函数,并且这是执行对象分配的所有其他资源(各种句柄、套接字...和由该对象在堆上创建的对象 - 如果它们不能比该对象存活得更久)。这用于 RAII 习惯用法

In addition to answers focused on the objects allocated on the heap (with new; which are deallocated only with delete)...Don't forget that if you place the object on the stack (so, without using new), its destructor will be called automatically and it will be removed from the stack (without calling delete) when it goes out of scope. So you have one function that is guaranteed to be executed when object goes out of the scope and that is perfect place to perform cleanup of all other resources allocated by the object (various handles, sockets...and objects created on the heap by this object - if they must not outlive this one). This is used in the RAII idiom.

雨夜星沙 2025-01-08 14:31:30

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

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

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

If delete deallocates the memory, then what's the need of the
destructor here?

The point of the destructor is to execute any logic required to clean up after your object, for example:

  • calling delete on other objects owned by the object being destroyed
  • properly releasing other resources like database connections; file handles and the like
嗳卜坏 2025-01-08 14:31:30

如果除了取消分配内存之外还需要执行其他操作,则需要调用析构函数。

除了非常简单的类之外,通常还有其他类。

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

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

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 内存会泄漏,最终会耗尽。


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

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

换句话说,您有类似的情况:

+-------------------------------------+
| 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 调用析构函数来“拆除”您的对象,并回收为该对象分配的内存。

You need to call the destructor in case there are other things that need to be done other than just de-allocating memory.

Other than very simple classes, there usually are.

Things like closing file handles or shutting down database connections, deleting other objects that are pointed to by members data within your object, and so forth.

A classic example is the implementation of a stack:

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.
}

Now think of what would happen if the destructor was not called every time one of your stacks got deleted. There is no automatic garbage collection in C++ in this case so the stackData memory would leak and you'd eventually run out.


This requiring of a destructor to delete all its resources moves down the tree towards the basic types. For example, you may have a database connection pool with an array of database connections. The destructor for that would delete each individual database connection.

A single database connection may allocate a lot of stuff, such as data buffers, caches, compiled SQL queries and so on. So a destructor for the database connection would also have to delete all those things.

In other words, you have something like:

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

Freeing the memory for the DB connection pool would not affect the existence of the individual DB connection or the other objects pointed to by them.

That's why I mentioned that only simple classes can get away without a destructor, and those are the classes that tend to show up at the bottom of that tree above.

A class like:

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

has no real need for a destructor since the memory deallocation is all you need to do.


The bottom line is that new and delete are opposite ends of the same pole. Calling new first allocates the memory then calls the relevant constructor code to get your object in a workable state.

Then, when you're done, delete calls the destructor to "tear down" your object the reclaims the memory allocated for that object.

拍不死你 2025-01-08 14:31:30

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

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

    ~something() {
        delete p;
    }

    int *p;
};

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

something *s = new something();

delete s;

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

Suppose you have a class that dynamically allocates memory:

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

    ~something() {
        delete p;
    }

    int *p;
};

Now let's dynamically allocate a something object:

something *s = new something();

delete s;

Now, if the delete didn't call the destructor, then s->p would never be freed. So delete has to both call the destructor and then deallocate the memory.

森林迷了鹿 2025-01-08 14:31:30

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

The destructor is in charge of freeing resources other than the object's allocated memory. For instance, if the object has a file handle open, the destructor could call fclose on it.

半窗疏影 2025-01-08 14:31:30

它释放该对象占用的内存。但是,对象分配的任何内容(以及该对象拥有的内容)都需要在析构函数中处理。

另外,一般来说...常见问题解答...通常不会错。

It deallocates the memory taken up by that object. However, anything that has been allocated by the object (and owned by that object) needs to be taken care of in the destructor.

Also, in general ... FAQs ... usually not wrong.

挽心 2025-01-08 14:31:30

如果您声明一个普通类(而不是指针),它会在程序关闭时自动调用构造函数并自动调用析构函数。如果您声明为指针,它会在使用 new 初始化时调用构造函数,并且不会自动调用析构函数,直到您使用 delete 调用删除该指针

if you declare a class normal (not pointer), it automatically calls constructor and call destructor automatically when the program closes. If you declare as pointer, it call the constructor when initializes using new and does not call destructor automatically until you call delete that pointer using delete

乖乖公主 2025-01-08 14:31:30

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

The destructor is there to clean up the changes that the object constructor and member functions might have done to the program state. That can be anything - remove the object from some global list, close an opened file, free allocated memory, close a database connection, etc.

空宴 2025-01-08 14:31:30

析构函数不会是一个强制功能。 C、Java、C# 等语言没有析构函数。 C++ 没有它也可以生存。

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

销毁意味着,对象作用域正式结束,任何对该对象的引用都将是非法的。例如:

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

在上面的代码中,obj是创建的A类型的static数据; foo() 返回对 obj 的引用,这是可以的,因为 obj.~A() 尚未被调用。假设 obj 是非静态的。代码将编译,但是,foo() 返回的 A* 现在指向一个不再是 A 对象的内存位置。意思是->操作错误/非法。

现在,您应该能够区分内存的释放和对象的销毁。两者紧密结合,但有一线之隔。

另请记住,析构函数可以在多个位置调用:

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

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

在破坏过程中,您可能想做一些有用的事情。假设A类在对象销毁时计算出一些结果;它应该打印计算结果。它可以用C 风格的方式完成(在每个return 语句中放置一些函数)。但~A() 是一个随时可用的一站式工具。

The destructor would not have been a mandatory feature. The languages like, C, Java, C# don't have destructors. C++ also can live without it.

Destructor is a special facility provided by C++ (same as Constructor). It's called when an object is "destroyed".

Destroy means, the object scope is officially finished and any reference to that object will be illegal. For example:

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

In above code, obj is a static data created of type A; foo() returns a reference to that obj which is ok, because obj.~A() is not yet called. Suppose obj is non-static. The code will compile, however, A* returned by foo() is now pointing to a memory location which is no more an A object. Means -> the operation is bad/illegal.

Now, you should be able to distinguish between deallocation of the memory and the destruction of the object. Both are tightly coupled, but there is a thin line.

Also remember that destructor can be called at multiple places:

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

In above example, obj.~A() will be called only once, but it can be called from any of the places shown.

During the destruction, you may want to do some useful stuff. Suppose class A calculates some result, when the object destroys; it should print the result of calculation. It can be done in C style way (putting some function at every return statement). But ~A() is a readily available one-stop facility.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文