C++核心——多態(tài)里的構造和析構函數

C++核心——多態(tài)里的構造和析構函數

一、構造函數和析構函數

對象的初始化和清理也是兩個非常重要的安全問題

? 一個對象或者變量沒有初始狀態(tài),對其使用后果是未知

? 同樣的使用完一個對象或變量,沒有及時清理,也會造成一定的安全問題

c++利用了構造函數析構函數解決上述問題,這兩個函數將會被編譯器自動調用,完成對象初始化和清理工作。

對象的初始化和清理工作是編譯器強制要我們做的事情,因此如果我們不提供構造和析構,編譯器會提供

編譯器提供的構造函數和析構函數是空實現。

  • 構造函數:主要作用在于創(chuàng)建對象時為對象的成員屬性賦值,構造函數由編譯器自動調用,無須手動調用。
  • 析構函數:主要作用在于對象銷毀前系統(tǒng)自動調用,執(zhí)行一些清理工作。
#include <iostream>
using namespace std;

class Test
{
public:
    Test();
    ~Test();
};

Test::Test()
{
    cout << "constructor" << endl;
}

Test::~Test()
{
    cout << "destructor" << endl;
}


int main()
{
    Test t;
    return 0;
}

******************運行結果************************
PS F:\C_CPP\CPP_study> g++ .\test.cpp
PS F:\C_CPP\CPP_study> .\a.exe
constructor
destrtor   
PS F:\C_CPP\CPP_study>  

二、繼承時構造函數和析構函數的執(zhí)行過程

構造函數:先執(zhí)行父類構造函數后執(zhí)行子類構造函數。

析構函數:先執(zhí)行子類析構函數再執(zhí)行父類析構函數。

<u>有點像棧區(qū)的順序,先進后出。創(chuàng)建類時先把父類push進去,所以先執(zhí)行父類的構造函數,再把子類PUSH進去,所以再執(zhí)行子類的構造函數。釋放的時候子類在上面,所以先被POP出來,所以先執(zhí)行子類的析構函數,再POP父類時執(zhí)行父類的析構函數。</u>

#include <iostream>
#include <string>
using namespace std;

class Base
{
public:
    string name;
    int age;
    virtual void getInfo() = 0;

    Base()
    {
        cout << "Base's constructor" << endl;
    }

    ~Base()
    {
        cout << "Base's destructor" << endl;
    }
};

class Son : public Base
{
public:
    Son(string name, int age);
    virtual void getInfo();
    ~Son();
};

Son::Son(string name, int age)
{
    cout << "son's constructor" << endl;
    this->name = name;
    this->age = age;
}

void Son::getInfo()
{
    cout << "name: " << this->name <<
    " \tage: " << this->age << endl;
}

Son::~Son()
{
    cout << "son's destructor" << endl;
}

void test1()
{
    Son *pson = new Son("tom", 20);
    pson->getInfo();
    delete pson;
}

int main()
{
    test1();
    return 0;
}

**********************執(zhí)行結果*********************************
PS F:\C_CPP\CPP_study> g++ .\01_虛析構函數.cpp
PS F:\C_CPP\CPP_study> .\a.exe
Base's constructor                      # 先執(zhí)行父類構造
son's constructor                       # 再執(zhí)行子類構造
name: tom       age: 20
son's destructor                        # 先執(zhí)行子類析構
Base's destructor                       # 再執(zhí)行父類析構
PS F:\C_CPP\CPP_study>

三、多態(tài)時的執(zhí)行順序

這個重點,先上代碼

#include <iostream>
#include <string>
using namespace std;

class Base
{
public:
    string name;
    int age;
    virtual void getInfo() = 0;

    Base()
    {
        cout << "Base's constructor" << endl;
    }

    ~Base()
    {
        cout << "Base's destructor" << endl;
    } 
};

class Son : public Base
{
public:
    Son(string name, int age);
    virtual void getInfo();
    ~Son();
};

Son::Son(string name, int age)
{
    cout << "son's constructor" << endl;
    this->name = name;
    this->age = age;
}

void Son::getInfo()
{
    cout << "name: " << this->name <<
    " \tage: " << this->age << endl;
}

Son::~Son()
{
    cout << "son's destructor" << endl;
}

void test2()
{
    Base *p = new Son("jack", 22);
    p->getInfo();
    delete p;
}

int main()
{
    test2();
    return 0;
}

*****************************執(zhí)行結果***********************************
PS F:\C_CPP\CPP_study> g++ .\01_虛析構函數.cpp
PS F:\C_CPP\CPP_study> .\a.exe
Base's constructor                      # 先執(zhí)行父類構造
son's constructor                       # 再執(zhí)行子類構造
name: tom       age: 20
Base's destructor                       # 再執(zhí)行父類析構
PS F:\C_CPP\CPP_study>    

看出設么區(qū)別了么,和上已個代碼相比,這里執(zhí)行了父類的析構函數,可是子類的構造函數并沒有執(zhí)行。為什么會這樣呢,接著從代碼的區(qū)別著手分析

void test1()
{
    Son *pson = new Son("tom", 20);
    pson->getInfo();
    delete pson;
}
************************************************
void test2()
{
    Base *p = new Son("jack", 22);
    p->getInfo();
    delete p;
}

其實兩套代碼的區(qū)別就是這個函數的不同,具體到函數就成了函數體的第一條語句的不同。在test1中創(chuàng)建的是Son類型的指針,所以在delete的時候刪除的是Son類型指針,那么肯定會執(zhí)行Son的析構函數,可是test2中創(chuàng)建的是Base類型指針,所以在delete是刪除的是Base類型的指針,而Son雖然繼承了Base,但是卻沒有重寫B(tài)ase的析構函數,所以執(zhí)行不到Son里面去,也就無法執(zhí)行Son的析構函數,因為壓根兒delete的就不是Son指針。如果是這樣的話,那么就會帶來一個很嚴重的問題,那就是如果Son類中含有堆取開辟的空間需要在析構函數中釋放,就會導無法釋放,造成內存泄漏。

一種致命警告??

class Base
{
public:
    string name;
    int age;
    virtual void getInfo() = 0;

    Base()
    {
        cout << "Base's constructor" << endl;
    }
};

如以上代碼,將Base類定義為這個樣子父類未定義析構函數時會造成警告,但是這個警告有的時候會導致程序無法正常運行下去,這個問題在我寫一個小項目時曾發(fā)生過,因為父類沒有析構函數體,變異時只有警告,但是運行時,就是執(zhí)行完這個語句后無法繼續(xù)執(zhí)行后面的語句,所以這里提醒各位遇到這種提示警告最好馬上解決,畢竟提示信息也很明顯

warning: delete called on 'Base' that is abstract but has non-virtual destructor [-Wdelete-non-virtual-dtor]
    delete p;
    ^
1 warning generated.

為了解決上述問題,需要使用多天的動態(tài)綁定特效

  • 靜態(tài)綁定:編譯時綁定,通過對象調用

  • 動態(tài)綁定:運行時綁定,通過地址實現

為了實現動態(tài)綁定,所以需要將父類的析構函數定義成虛函數,從而使調用父類西溝函數時動態(tài)綁定到子類的析構函數上。當然這里編譯器還幫我們干了一件事,就是編譯器把父類的析構函數數和子類的析構函數進行了一個綁定,因為正常情況下,只有子類重寫了父類函數才會被動態(tài)綁定,但是這里雖然兩個析構函數名都不一樣,但是仍然能動態(tài)綁定,這就是編譯器進行的一種綁定。

#include <iostream>
#include <string>
using namespace std;

class Base
{
public:
    string name;
    int age;
    virtual void getInfo() = 0;

    Base()
    {
        cout << "Base's constructor" << endl;
    }
  
    virtual ~Base()
    {}  
};

class Son : public Base
{
public:
    Son(string name, int age);
    virtual void getInfo();
    ~Son();
};

Son::Son(string name, int age)
{
    cout << "son's constructor" << endl;
    this->name = name;
    this->age = age;
}

void Son::getInfo()
{
    cout << "name: " << this->name <<
    " \tage: " << this->age << endl;
}

Son::~Son()
{
    cout << "son's destructor" << endl;
}

void test1()
{
    Son *pson = new Son("tom", 20);
    pson->getInfo();
    delete pson;
}

void test2()
{
    Base *p = new Son("jack", 22);
    p->getInfo();
    delete p;
}

int main()
{
    test2();
    return 0;
}

**************************執(zhí)行結果*****************************
PS F:\C_CPP\CPP_study> g++ .\01_虛析構函數.cpp
PS F:\C_CPP\CPP_study> .\a.exe
Base's constructor
son's constructor
name: jack      age: 22
son's destructor
PS F:\C_CPP\CPP_study>  

從代碼的運行結果來看,成功調用的子類的的虛構函數,如果子類中有堆區(qū)的數據,就可以正常在析構函數中釋放。有人看到這里可能會有疑問,那父類的析構沒有執(zhí)行怎么辦?這個問題完全不用擔心,因為一般在實現多臺的時候,父類被定義為抽象類,無法實例化對象,內部函數都別子類重寫,調用的時候實質是在調用子類的函數。

四、總結

其實這篇博文是在我做一個小項目時遇到上面的警告,沒有在意,導致程序無法運行,然后查資料并總結出來的。這里總結一句,就是在實現多態(tài)的時候,定義抽象父類的時候記得定義虛析構函數,養(yǎng)成這個習慣,每次定義多態(tài)的父類時順手就把析構定義成虛函數就可以,可以省去很多不必要的麻煩。

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容