【C++深度剖析學習總結】 22 對象的銷燬和臨時對象的概念

對象的銷燬

作者 CodeAllen ,轉載請註明出處


1.對象的銷燬
生活中的對象都是被初始化後才上市的
生活中的對象被銷燬前會做一些清理工作

問題:C++中如何清理需要銷燬的對象?
一般而言,需要銷燬的對象都應該做清理

解決方案

  • 爲每個類都提供一個public的free函數
  • 對象不再需要時立即調用free函數進行清理
    在這裏插入圖片描述
    存在的問題
  • free只是一個普通的函數,必須顯示的調用
  • 對象銷燬前沒有做清理,很可能造成資源泄漏
  • C++編譯器是否能夠自動調用某個特殊的函數進行對象的清理?

2.析構函數
C++的類中可以定義一個特殊的清理函數

  • 這個特殊的清理函數叫做析構函數
  • 析構函數的功能與構造函數相反

定義:~ClassName()

  • 析構函數沒有參數也沒有返回值類型聲明
  • 析構函數在對象銷燬時自動被調用

析構函數使用

#include <stdio.h>

class Test
{
    int mi;
public:
    Test(int i)
    {
        mi = i;
        printf("Test(): %d\n", mi);
    }
    ~Test()
    {
        printf("~Test(): %d\n", mi);
    }
};

int main()
{
    Test t(1);
    
    Test* pt = new Test(2);
    
    delete pt;
    
    return 0;
}

/*
從結果可以看出來,delete時候釋放了pt,函數return的時候釋放了t
Test(): 1
Test(): 2
~Test(): 2
~Test(): 1
*/

析構函數的定義準則

  • 當類中自定義了析構函數,並且構造函數中使用了系統資源(如:內存申請,文件打開等),則需要自定義析構函數

小結
析構函數是對象銷燬時進行清理的特殊函數
析構函數在對象銷燬時自動被調用
析構函數是對象釋放系統資源的保障


臨時對象的概念

作者 CodeAllen ,轉載請註明出處


1.有趣的問題
下面的程序輸出什麼?爲什麼?
在這裏插入圖片描述
有趣的問題

#include <stdio.h>

class Test {
    int mi;
public:
    Test(int i) {
        mi = i;
    }
    Test() {
        Test(0);  //等價於沒有,過了這一行就沒了
    }
    void print() {
        printf("mi = %d\n", mi);
    }
};

int main()
{
    Test t;
    
    t.print();

    return 0;
}

2.發生了什麼?
程序意圖:

  • 在Test()中以0作爲參數調用Test(int i)
  • 將成員變量mi的初始值設置爲0
    運行結果:
  • 成員變量mi的值爲隨機值
  • 究竟哪個地方出了問題? 臨時對象的出現

3.思考
構造函數是一個特殊的函數

  • 是否可以直接調用?
  • 是否可以在構造函數中調用構造函數?
  • 直接調用構造函數的行爲是什麼?

4.答案

  • 直接調用構造函數將產生一個臨時對象
  • 臨時對象的生命週期只有一條語句的時間
  • 臨時對象的作用域只在一條語句中
  • 臨時對象是C++中值得警惕的灰色地帶

解決方案

#include <stdio.h>
class Test {
    int mi;
   
    void init(int i)  //私有的初始函數
    {
        mi = i;
    }
public:
    Test(int i) {
        init(i);   //用的時候調用,就不會產生臨時對象
    }
    Test() {
        init(0);
    }
    void print() {
        printf("mi = %d\n", mi);
    }
};
int main()
{
    Test t;
   
    t.print();
    return 0;
}

5.編譯器的行爲
現代C++編譯器在不影響最終執行結果的前提下,會盡力減少臨時對象的產生!!!

臨時對象

#include <stdio.h>
class Test
{
    int mi;
public:
    Test(int i)
    {
        printf("Test(int i) : %d\n", i);
        mi = i;
    }
    Test(const Test& t)
    {
        printf("Test(const Test& t) : %d\n", t.mi);
        mi = t.mi;
    }
    Test()
    {
        printf("Test()\n");
        mi = 0;
    }
    int print()
    {
        printf("mi = %d\n", mi);
    }
    ~Test()
    {
        printf("~Test()\n");
    }
};
Test func()
{
    return Test(20);
}
int main()
{
    Test t = Test(10); //預計運行過程: 1.生成臨時對象 2.用臨時對象初始化t對象
                       //==>調用拷貝構造函數,實際運行過程:==> Test t = 10; 推薦這麼寫就可以避免臨時對象
    Test tt = func();  // ==> Test tt = Test(20); ==> Test tt = 20;
   
    t.print();
    tt.print();
   
    return 0;
}

小結
直接調用構造函數將產生一個臨時對象
臨時對象是性能的瓶頸,也是bug的來源之一
現代C++編譯器會盡力避開臨時對象
實際工程開發中需要人爲的避開臨時對象

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章