banner
Matrix

Matrix

Abyss
email
github

Petty C++ - 物件切割

對象切割 Object Slicing 算是 C++ 的一個常見的多態問題。當一個派生類對象賦值給一個基類時,會丟棄掉派生部分,只保留基類對象部分,造成一些意料之外的結果。

對象切割示例#

考慮下面的代碼:

class Animal {
public:
    virtual string GetType() const { return "Animal"; }
    virtual string GetVoice() const { return "Voice"; }
};

class Dog : public Animal {
public:
    string GetType() const override { return "Dog"; }
    string GetVoice() const override { return "Woof"; }
};

class Cat : public Animal {
public:
    string GetType() const override { return "Cat"; }
    string GetVoice() const override { return "Miaow"; }
};

void Type(Animal& a) { cout<<a.GetType(); }
void Speak(Animal& a) { cout<<a.GetVoice(); }

int main()
{
    Dog d; Type(d); cout<<" speak "; Speak(d); cout<<" - ";
    Cat c; Type(c); cout<<" speak "; Speak(c); cout<<endl;
    return 0;
}

這裡的正確輸出:

"Dog speak Voice - Cat speak Voice"

Speak 函數的聲明確實使用的是按值傳遞,這會導致對象切割。在這種情況下,傳遞給 Speak 函數的 Animal 對象只會保留 Animal 基類部分的信息,派生類的信息(如 Dog 或 Cat 的特定實現)將會丟失。

避免切割#

這個問題可以通過改變 Speak 函數,使其接收引用而非對象本身來解決:

void Speak(Animal& a) { cout << a.GetVoice(); }

這樣修改後,多態性將得到正確應用,Speak (d) 將輸出 "Woof",Speak (c) 將輸出 "Miaow"。

也可以創建了重載,比如void Speak(Dog d)void Speak(Cat c),那麼會調用正確的函數。

實際上最常用的方法仍然是使用引用或者指針保留多態行為,重載的做法限制了代碼的泛化性和可擴展性。

虛函數和多態#

如果不使用虛函數,那麼方法調用是靜態的,也就是說在編譯時決定調用的函數,此時的調用行為取決於聲明的類型,而不是實際類型。 也就是說:

沒有虛函數,即使避免了對象切割(例如通過傳引用或指針),派生類的方法也不會通過基類的引用或指針被調用。在通過基類的指針或引用調用函數時,總是會調用基類的方法,而不是派生類中可能存在的重寫版本。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。