2007年11月20日 星期二

動態繫結(dynamic binding)寫法

C++程式是個物件導向的程式, 而繼承則是物件導向中的重要角色, 以下是最近採用的新寫法, 若以基礎類別宣告的指標, 指向衍生類別的物件, 若基礎類別與衍生類別有相同名稱的函式, 也就是說衍生類別複寫了該函式, 配合虛擬函式宣告, 它可以形成動態繫結(binding), 因此會依據函式本身去選用對應的函式, 而非直接採用指標型別所屬函式

範例程式碼1

#include <iostream>

using namespace std;


class mammal {

public:

    mammal () { cout << "Class mammal constructor\n"; }

    virtual ~mammal () { cout << "Class mammal destructor\n"; }

    void virtual speak() const { cout << "This is mammal's speaking\n"; }

};

class dog: public mammal {

public:

    dog () { cout << "Class dog constructor\n"; }

    virtual ~dog () { cout << "Class dog destructor\n"; }

    void virtual speak() const { cout << "This is dog's speaking\n"; }

};


int main() {

    mammal *pm[2];

    // mammal

    pm[0] = new mammal;

    pm[0]->speak();

    delete pm[0];

    

    // dog

    pm[1] = new dog;

    pm[1]->speak();

    delete pm[1];

    return 0;

};

說明1: 宣告一個哺乳類(mammal)類別, 它除了建構子與解構子外, 只有提供speak函式
說明2: 宣告一個狗類(dog)類別, 它公開繼承mammal, 另外它還複寫了speak函式
說明3: 宣告mammal類別的解構子與speak函式為虛擬函式(virtual)
說明4: 主程式中, 以mammal宣告指標, 當指標指往mammal物件時, 並呼叫speak, 它會啟動mammal版本的speak; 若指向dog物件時, 並呼叫speak, 它會自動切換至dog版本的speak
說明5: 若取消mammal版本speak函式的虛擬宣告, 則同樣的程式碼, 即使指向dog物件, 仍舊啟動mammal版本的speak
說明6: 若取消mammal版本解構子的虛擬宣告, 則在delete pm[1]時, 會發現只有啟用mammal版本的解構子, 而依照順序由dog->mammal依次解構
說明7: 解構時會依據繼承的順序, 從衍生類別解構子開始呼叫起, 逐步往基本類別解構子移動; 而相反地, 建構時則與解構相反

這樣的寫法, 可以讓程式碼變成用同樣的名字來呼叫不同功能的函式, 使得較大規模程式開發, 可以更加模組化, 更有架構...

這樣的寫法裡面, 用基本類別的指標去呼叫基本類別的專屬函式, 並沒有問題...只是今天的問題是, 若是衍生類別有不同的函式(非複寫),  則會變成基礎類別的指標無法使用衍生類別的擴充函式, 以下列出兩種修改方式

修改1

#include <iostream>

using namespace std;


class mammal {

public:

    mammal () { cout << "Class mammal constructor\n"; }

    virtual ~mammal () { cout << "Class mammal destructor\n"; }

    void virtual speak() const { cout << "This is mammal's speaking\n"; }

    void method01() const { cout << "This is method01\n"; }

    void virtual method02() const { cout << "This is method02\n"; }

};

class dog: public mammal {

public:

    dog () { cout << "Class dog constructor\n"; }

    virtual ~dog () { cout << "Class dog destructor\n"; }

    void virtual speak() const { cout << "This is dog's speaking\n"; }

    void method02() const { cout << "This is method02\n"; }

};


int main() {

    mammal *pm[2];

    // mammal

    pm[0] = new mammal;

    pm[0]->speak();

    pm[0]->method01();

    delete pm[0];

    

    // dog

    pm[1] = new dog;

    pm[1]->speak();

    pm[1]->method01();

    pm[1]->method02();

    delete pm[1];

    return 0;

};


修改2

#include <iostream>

using namespace std;


class mammal {

public:

    mammal () { cout << "Class mammal constructor\n"; }

    virtual ~mammal () { cout << "Class mammal destructor\n"; }

    void virtual speak() const { cout << "This is mammal's speaking\n"; }

    void method01() const { cout << "This is method01\n"; }

};

class dog: public mammal {

public:

    dog () { cout << "Class dog constructor\n"; }

    virtual ~dog () { cout << "Class dog destructor\n"; }

    void virtual speak() const { cout << "This is dog's speaking\n"; }

    void method02() const { cout << "This is method02\n"; }

};


int main() {

    mammal *pm[2];

    // mammal

    pm[0] = new mammal;

    pm[0]->speak();

    pm[0]->method01();

    delete pm[0];

    

    // dog

    pm[1] = new dog;

    pm[1]->speak();

    pm[1]->method01();

    static_cast(pm[1])->method02();

    delete pm[1];

    return 0;

};


修改一則是直接在基本類別內加上一個虛擬函式, 使得整個狀況直接變成動態繫結的問題, 這樣的缺點是那基本類別的函式會多如牛毛; 修改二則是在呼叫時, 直接將基本類別的指標強制轉型為衍生類別的指標, 感覺上這個邏輯上比較正確...

0 意見: