指向派生类的基类指针无法访问派生类方法

时间:2019-10-15 02:25:42

标签: c++ c++11 virtual-functions dynamic-binding method-hiding

我正在学习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中的函数地址相同。

由于Dragondescribe的{​​{1}}是不同的原型,因此它在继承了原始int dummy的{​​{1}}之后被添加到Dragon的vtable中来自敌人。但是无法从describe访问int dummy版本,因为Enemy*的vtable甚至没有该偏移量。

这正确吗?

3 个答案:

答案 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"; }
};