我正在学习C ++ 11中的继承,我发现如果派生类重新定义了虚函数名称,但原型不同,则分配有指向派生类的指针的基类指针只能访问函数的基类版本。无法访问派生的版本功能。我不知道为什么会这样。
class Enemy {
public:
virtual void describe() { std::cout << "Enemy"; }
};
class Dragon : public Enemy {
public:
virtual void describe(int dummy) { std::cout << "Dragon"; }
};
在main
中,
Dragon foo;
Enemy* pe = &foo;
pe->describe(); // Enemy
foo.describe(1); // Dragon
pe->describe(1); // no matching function, candidate is Enemy::describe()
根据我对虚函数表的了解,pe
指向的派生对象(即foo
)应具有指向Dragon
的vtable的vpointer成员。我也知道,在派生类中重新定义函数名称将在基类中隐藏相同名称的所有函数。因此,在Dragon的vtable中,“ describe”的地址应为带有参数int dummy
的函数。
但是事实证明pe
可以访问Enemy
的方法版本,该版本应该被隐藏了。 pe
无法访问Dragon
的方法版本,该版本应该在pe
的vtable中。就像使用Enemy
的vtable一样执行。为什么会这样?
更新: 我认为现在我或多或少了解其背后的机制。这是我的假设:
由于它是指向Enemy
的指针,因此程序将首先在Enemy
的范围内找到方法名称。如果找不到该名称,则编译器将给出错误。如果不是虚拟的,则调用它。如果它是虚拟的,则将方法的偏移量记录在Enemy
的vtable中。然后程序使用此偏移量访问目标对象的vtable中的正确方法。
如果适当地重写了该方法,则将更改目标对象的vtable中该偏移量处的函数地址。否则,它将与示例中的Enemy
vtable中的函数地址相同。
由于Dragon
与describe
的{{1}}是不同的原型,因此它在继承了原始int dummy
的{{1}}之后被添加到Dragon
的vtable中来自敌人。但是无法从describe
访问int dummy
版本,因为Enemy*
的vtable甚至没有该偏移量。
这正确吗?
答案 0 :(得分:0)
名称相同但签名不同的功能本质上是不同的功能。
通过在virtual void describe(int dummy)
类中声明Dragon
,可以声明一个新的虚函数,而不是覆盖原始虚函数(virtual void describe()
中的Enemy
)。您只能覆盖具有相同签名的虚函数。
您无法在指向describe(1)
的指针上调用Enemy
,因为c ++会根据实例的编译时间类型调用函数(尽管可以动态分派调用以调用实际的overrode方法)。
答案 1 :(得分:0)
在C ++中,具有相同名称但参数不同的函数是完全独立的函数, 彼此无关 。它们具有相同的名称是完全无关紧要的。
与您在基类“ apple”中调用该函数以及在派生类“ banana”中调用该函数完全相同。由于在基类中没有“香蕉”功能,因此您显然不能在基类中调用它。派生类中的banana函数显然不会覆盖基类中的函数。
我也知道在派生类中重新定义函数名称 将在基类中隐藏所有相同名称的函数。
那是不正确的。仅当名称相同但参数相同(以及任何限定词,如果有或没有)时,它才会隐藏。
答案 2 :(得分:0)
实际上您有:
class Enemy {
public:
virtual void describe() { std::cout << "Enemy"; }
};
class Dragon : public Enemy {
public:
// void describe() override { Enemy::describe(); } // Hidden
virtual void describe(int dummy) { std::cout << "Dragon"; }
};
重载方法的选择是静态完成的:
Enemy
上的指针/引用仅见void Enemy::describe()
Dragon
上的指针/引用仅看到void Dragon::describe(int)
(但可以显式访问void Enemy::describe()
)。
然后使用运行时类型完成虚拟调度。
所以
Dragon foo;
Enemy* pe = &foo;
foo.describe(); // KO: Enemy::describe() not visible (1)
foo.Enemy::describe(); // OK: Enemy::describe()
foo.describe(1); // OK: Dragon::describe(int)
pe->describe(); // OK: Enemy::describe()
pe->describe(1); // KO: No Enemy::describe(int)
pe->Dragon::describe(1);// KO: Dragon is not a base class of Enemy
(1)可以通过使用{p>更改Dragon
来解决
class Dragon : public Enemy {
public:
using Enemy::describe; // Unhide Enemy::describe()
virtual void describe(int dummy) { std::cout << "Dragon"; }
};