C++虚拟虚空与无虚拟

C++ Virtual Void vs no virtual

本文关键字:虚拟 C++      更新时间:2023-10-16

我对虚拟函数感到困惑。有人告诉我,父类中的虚拟意味着我可以在子类中覆盖它。然而,如果我省略了父类中的虚拟,我仍然可以覆盖它

#include <iostream>
using namespace std;
class Enemy{
public:
//if I remove virtual, it still gets overriden in child class
virtual void attack(){ 
cout << "Attack Enemy!" << endl;
}
};

class Minion : public Enemy {
public:
void attack(){
cout << "Attack Minon!" << endl;
}
};
int main() {
Minion m;
m.attack();
}

如果函数是虚拟的,则在运行时通过vtable将调用动态调度到派生类型提供的实现,而如果没有,则编译器在编译时查看对象并选择静态类型的类方法。如果你有一个指向基类的指针,这意味着使用了基本实现,如果函数不是虚拟的:

Enemy *e = new Minion(); e->attack();

如果attack不是虚拟的,将打印"攻击敌人!"。当使用虚拟函数时,编译器将插入一些逻辑以在运行时查找正确的实现,并且当执行e->attack()时,在e指向的对象指针的vtable中查找该实现,发现Minion覆盖attack,从而打印"Attack Minion!"。

如果你想强制你的派生类覆盖基类方法,你也可以通过使用使函数完全虚拟

virtual void attack() = 0;

您可以重写有问题的方法,理想情况下使用override关键字声明该方法,以允许编译器在您没有重写任何内容时进行抱怨。

class Enemy {
public:
virtual ~Enemy() = default; /* Don't forget... or non-virtual protected. */
virtual void attack() {}
};
class Minion : public Enemy {
public:
void attack() override {}
};

您的困惑可能源于这样一个事实,即您同样可以很好地隐藏基类方法。

class Enemy {
public:
virtual ~Enemy() = default;
void attack() {}
};
class Minion : public Enemy {
public:
void attack() {}
};

这不仅起到了不同的作用,而且还将成为命名异常糟糕的一个例子,因为这让读者感到困惑:在类层次结构的上下文中,具有相同签名的相等成员函数名不可避免地与重写方法相关联。

您还没有覆盖它,只是添加了另一个具有相同签名的方法。

Enemy* enemy = new Minion();
enemy->attack();
delete enemy;

如果this调用Minion中的代码,那么您做得对。如果它调用Enemy中的代码,则表明您做错了什么。你需要virtual来做正确的事情。