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),那么会调用正确的函数.

实际上最常用的方法仍然是使用引用或者指针保留多态行为,重载的做法限制了代码的泛化性和可扩展性。

虚函数和多态#

如果不使用虚函数,那么方法调用是静态的,也就是说在编译时决定调用的函数,此时的调用行为取决于声明的类型,而不是实际类型。 也就是说:

没有虚函数,即使避免了对象切割(例如通过传引用或指针),派生类的方法也不会通过基类的引用或指针被调用。在通过基类的指针或引用调用函数时,总是会调用基类的方法,而不是派生类中可能存在的重写版本。

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。