簡體   English   中英

由於delete運算符釋放內存,為什么我需要析構函數?

[英]As the delete operator deallocates memory, why do I need a destructor?

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

記住:delete p有兩件事:它調用析構函數並釋放內存。

如果刪除釋放內存,那么析構函數需要什么?

如果刪除釋放內存,那么析構函數需要什么?

析構函數的要點是執行在對象之后清理所需的任何邏輯,例如:

  • 在被銷毀對象擁有的其他對象上調用delete
  • 正確釋放數據庫連接等其他資源; 文件句柄等

你需要調用析構函數,以防除了分配內存之外還需要做其他事情。

除了非常簡單的課程,通常都有。

關閉文件句柄或關閉數據庫連接,刪除對象中成員數據指向的其他對象等等。

一個典型的例子是堆棧的實現:

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調用析構函數“拆除”你的對象,回收為該對象分配的內存。

假設您有一個動態分配內存的類:

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

    ~something() {
        delete p;
    }

    int *p;
};

現在讓我們來動態分配something對象:

something *s = new something();

delete s;

現在,如果delete沒有調用析構函數,那么s->p永遠不會被釋放。 因此delete必須同時調用析構函數然后釋放內存。

析構函數負責釋放除對象分配的內存之外的資源。 例如,如果對象打開了文件句柄,則析構函數可以在其上調用fclose

這將釋放對象所占用的內存。 但是,對象分配任何內容( 該對象擁有)都需要在析構函數中處理。

此外,一般來說......常見問題解答...通常沒有錯。

如果聲明一個類正常(不是指針),它會自動調用構造函數並在程序關閉時自動調用析構函數。 如果你聲明為指針,它在使用new初始化時調用構造函數,並且在你使用delete調用delete指針之前不會自動調用析構函數

析構函數用於清除對象構造函數和成員函數可能對程序狀態所做的更改。 這可以是任何東西 - 從某個全局列表中刪除對象,關閉打開的文件,釋放已分配的內存,關閉數據庫連接等。

析構函數不是必需的功能 像C,Java,C#這樣的語言沒有析構函數。 C ++也可以沒有它。

析構函數是C ++提供的一種特殊工具 (與Constructor相同)。 當一個對象被“摧毀”時調用它。

銷毀意味着,對象范圍正式完成,對該對象的任何引用都是非法的。 例如:

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()只會調用一次,但可以從顯示的任何位置調用它。

在破壞期間,您可能想要做一些有用的東西。 假設當對象破壞時, class A計算一些結果; 它應該打印計算結果。 它可以用C風格的方式完成(在每個return語句中放置一些函數)。 ~A()是一個隨時可用的一站式設施。

除了專注於堆上分配的對象的答案(使用new ;僅使用delete解除分配)...不要忘記,如果將對象放在堆棧上(所以,不使用new ),它的析構函數將自動調用,當它超出范圍時,它將從堆棧中刪除 (不調用delete )。 因此,當對象超出范圍時,您有一個保證執行的功能,這是執行對象分配的所有其他資源(各種句柄,套接字......和由此在堆上創建的對象)的完美位置對象 - 如果他們不能超過這一個)。 這用於RAII習語

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM