【C++】 55_經典問題分析 四

TianSong發表於2019-05-14

關於動態記憶體分配

new 和 malloc 的區別是什麼?
delete 和 free 的區別是什麼?

  • new 關鍵字和 malloc 函式的區別

    • new 關鍵字是 C++ 的一部分
    • malloc 是 C 庫提供的函式
    • new 以具體型別為單位進行記憶體分配
    • malloc 以位元組為單位進行記憶體分配
    • new 在申請記憶體空間時可進行初始化
    • malloc 僅根據需要申請定量的記憶體空間

程式設計實驗: new、delete 與 malloc、free

#include <iostream>
#include <cstdlib>

using namespace std;

class Test
{
private:
    int* mp;
public:
    Test()
    {
        cout << "Test::Test()" << endl;
        
        mp = new int(100);
    }
    ~Test()
    {
        delete mp;
        
        cout << "~Test::Test()" << endl;
    }
};

int main()
{
    Test* pn = new Test;
    Test* pm = (Test*)malloc(sizeof(Test));

    delete pn;
    free (pm);

    return 0;
}
輸出:
Test::Test()
~Test::Test()

分析:

new Test;                    ==> 在堆上建立一個物件,建構函式被呼叫
(Test*)malloc(sizeof(Test)); ==> 在堆上申請 sizeof(Test) 大小記憶體,建構函式未被呼叫,物件未正常建立

delete pn; ==> 銷燬物件, 歸還記憶體,解構函式被呼叫
free(pm);  ==> 僅歸還記憶體,解構函式未被呼叫

當 delete 與 free 混用,會發生什麼呢?

delete –> free :

int main()
{
    Test* pn = new Test;

    free (pn);          // 注意這裡!

    return 0;
}
輸出:
Test::Test()

分析:
free 可以釋放 new 申請的堆空間,但解構函式未被呼叫,物件未正常銷燬(例項中,導致系統資源洩漏!!)

free –> delete :

int main()
{
    Test* pm = (Test*)malloc(sizeof(Test));
      
    delete pm;         // 注意這裡!

    return 0;
}
輸出:
~Test::Test()

分析:
delete 可以釋放 malloc 申請的堆空間,不合法物件的解構函式被呼叫!執行結果將是不確定的!(示例中,將delete一個野指標指向的記憶體空間)

結論: C++ 中杜絕 malloc、 free 的使用

  • new 和 malloc 的區別

    • new 在所有 C++ 編譯器中都被支援
    • malloc 在某些系統開發中不能呼叫
    • new 能夠觸發建構函式的呼叫
    • malloc 僅分配需要的記憶體空間
    • 物件的建立只能使用 new
    • malloc 不適合物件導向開發

  • delete 和 free 的區別

    • delete 在所有 C++ 編譯器中都被支援
    • free 在某些系統開發中不能呼叫
    • delete 能夠觸發解構函式的呼叫
    • free 僅歸還之前分配的記憶體空間
    • 物件的銷燬只能使用 delete
    • free 不適合物件導向開發

關於虛擬函式

建構函式是否可以成為虛擬函式?
解構函式是否可以成為虛擬函式?

  • 建構函式不可能成為虛擬函式

    • 在建構函式執行結束後,虛擬函式表指標才會被正確的初始化
  • 解構函式可以成為虛擬函式

    • 解構函式在物件銷燬之前被呼叫,意味著虛擬函式表指標仍然正確的指向虛擬函式表
    • 建議在設計類時將解構函式宣告為虛擬函式

程式設計實驗: 構造,析構,虛擬函式

test_1.cpp

#include <iostream>

using namespace std;

class Base
{
public:
    Base()
    {
        cout << "Base()" << endl;
    }
    virtual void func()
    {
        cout << "Base::func()" << endl;
    }
    ~Base()
    {
        cout << "~Base()" << endl;
    }
};

class Derived : public Base
{
public:
    Derived()
    {
        cout << "Derived()" << endl;
    }
    virtual void func()
    {
        cout << "Derived::func()" << endl;
    }
    ~Derived()
    {
        cout << "~Derived()" << endl;
    }
};

int main()
{
    Base* p = new Derived();    // 注意這裡!
    
    // ...
    
    delete p;

    return 0;
}
輸出:
Base()
Derived()
~Base()

分析: 
為什麼 子類 的解構函式沒有被呼叫呢?

Base* p = new Derived(); ==> 因為賦值相容性,編譯通過。
delete p;                ==> 編譯器考慮安全性,根據指標型別進行物件銷燬

解構函式宣告為虛擬函式的意義 1:test_2.cpp

#include <iostream>

using namespace std;

class Base
{
public:
    Base()
    {
        cout << "Base()" << endl;
    }
    virtual void func()
    {
        cout << "Base::func()" << endl;
    }
    virtual ~Base()               // 注意這裡!
    {
        cout << "~Base()" << endl;
    }
};

class Derived : public Base
{
public:
    Derived()
    {
        cout << "Derived()" << endl;
    }
    virtual void func()
    {
        cout << "Derived::func()" << endl;
    }
    ~Derived()
    {
        cout << "~Derived()" << endl;
    }
};

int main()
{
    Base* p = new Derived();
    
    // ...
    
    delete p;

    return 0;
}
輸出:
Base()
Derived()
~Derived()
~Base()

分析:
當解構函式為虛擬函式時, delete p; 將根據 p 指向的實際物件決定如何呼叫解構函式。

解構函式發生多型行為,保證系統資源儘可能得到釋放!

當宣告建構函式為虛擬函式時,g++ 報錯: virtual Base() { }
error: constructors cannot be declared virtual   

建構函式中是否可以發生多型?
解構函式中是否可以發生多型?

  • 建構函式中不可能發生多型行為

    • 在建構函式執行時,虛擬函式表指標未正確初始化
  • 解構函式中不可能發生多型行為

    • 在解構函式執行時,虛擬函式表指標已經被銷燬

建構函式和解構函式中不能發生多型行為, 只呼叫當前類中定義的函式版本!

關於繼承中的強制型別轉換

繼承中如何正確的使用強制型別轉換?

  • dynamic_cast 是與繼承相關的型別轉換關鍵字
  • dynamic_cast 要求相關的類中必須有虛擬函式
  • 用於直接或間接繼承關係的指標(引用)之間

    • 指標:

      • 轉換成功: 得到目標型別指標
      • 轉換失敗: 得到一個空指標
    • 引用:

      • 轉換成功: 得到目標型別引用
      • 轉換失敗: 得到一個異常操作資訊

  • 編譯器會檢查 dynamic_cast 的使用是否正確
  • 型別轉換的結果只可能在執行階段才能得到

程式設計實驗: dynamic_cast 的使用

#include <iostream>

using namespace std;

class Base
{
public:
    Base()
    {
        cout << "Base()" << endl;
    }
    virtual ~Base()
    {
        cout << "~Base()" << endl;
    }
};

class Derived : public Base
{
};

int main()
{
    Base* p = new Base();                    // 注意這裡! 
    
    Derived* pd = dynamic_cast<Derived*>(p); // 注意這裡!
    
    if( pd != NULL )
    {
        cout << "pd = " << pd << endl;
    }
    else
    {
        cout << "Cast error!" << endl;
    }

    delete p;

    return 0;
}
輸出:
Base()
Cast error!
~Base()

解構函式宣告為虛擬函式的意義 2 :
解構函式被宣告為虛擬函式,保證 dynamic_cast 關鍵字可以被支援,而無需單獨刻意定義其它虛成員函式

小結

  • new / delete 會觸發建構函式或者解構函式
  • 建構函式不能成為虛擬函式
  • 解構函式可以成為虛擬函式(推薦解構函式成為虛擬函式)
  • 建構函式和解構函式中都無法產生多型行為
  • dynamic_cast 是與繼承相關的專用轉換關鍵字

以上內容參考狄泰軟體學院系列課程,請大家保護原創!

相關文章