指向派生 class 的 Base-class 指针无法访问派生-class 方法

Base-class pointer pointing to a derived class cannot access derived-class method

我正在学习 C++11 中的继承,我发现如果派生的 class 重新定义了一个虚函数名称但具有不同的原型,则 base-class 指针分配为指向派生 class 的指针只能访问函数的基础 class 版本。无法访问派生版本函数。我想知道为什么会这样。

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 成员。我还知道在派生 class 中重新定义一个函数名会隐藏基 class 中所有同名的函数。所以在Dragon的vtable中'describe'的地址应该是参数为int dummy的函数。

但事实证明 pe 可以访问 Enemy 的方法版本,这应该是隐藏的。 pe 无法访问 Dragon 的方法版本,它应该在 pe 的 vtable 中。它的执行就好像使用了 Enemy 的 vtable 一样。为什么会这样?

更新: 我想现在我或多或少地了解了它背后的机制。这是我的假设:

由于是指向Enemy的指针,程序会先在Enemy的作用域中查找方法名。如果找不到该名称,编译器会报错。如果它不是虚拟的,则调用它。如果是virtual,则在Enemy的vtable中记录方法的偏移量。然后程序使用这个偏移量访问目标对象的 vtable 中的正确方法。

如果该方法被正确覆盖,目标对象的 vtable 中该偏移量处的函数地址将被更改。否则,它将与 Enemy 的 vtable 中的函数地址相同,如示例中所示。

由于 Dragondescribeint dummy 是不同的原型,因此它被添加到 Dragon 的 vtable 中原来的 describe 之后它继承自 Enemy。但是 int dummy 版本无法从 Enemy* 访问,因为 Enemy 的 vtable 甚至没有那个偏移量。

这是正确的吗?

同名不同签名的函数本质上是不同的函数。

通过在 Dragon class 中声明 virtual void describe(int dummy),您已经声明了一个新的虚函数,而不是覆盖原来的虚函数(virtual void describe() in Enemy ).您只能覆盖具有相同签名的虚函数。

您不能在指向 Enemy 的指针上调用 describe(1),因为 c++ 根据实例的编译时类型调用函数(尽管可以动态调度此类调用以调用实际的覆盖方法)。

在C++中,同名不同参数的函数是完全独立的函数,没有任何关系 .他们同名这一事实完全无关紧要。

这与您在基 class "apple" 和派生 class "banana" 中调用函数完全相同。由于基class中没有"banana"函数,显然不能在基class中调用它。 derived class 中的 banana 函数显然没有覆盖 base class.

中的函数

I also know that redefinition of a function name in the derived class will hide all the functions of the same name in the base class.

这是不正确的。只有当它具有相同的名称和相同的参数(以及任何限定符,如果有或没有)时它才会隐藏它。

事实上你有:

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

重载方法的选择是静态完成的:

  • pointers/references 在 Enemy 上只能看到 void Enemy::describe()

  • pointers/references 在 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) 可以通过将 Dragon 更改为

来修复
class Dragon : public Enemy {
public:
  using Enemy::describe; // Unhide Enemy::describe()

  virtual void describe(int dummy) { std::cout << "Dragon"; }
};