1

我在学习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指向 (ie 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's describewith是一个不同的原型,它在它从 Enemy 继承的原始int dummy原型之后添加到Dragon's vtable 中。describe但是int dummy无法访问该版本,Enemy*因为Enemy' 的 vtable 甚至没有那个偏移量。

这个对吗?

4

3 回答 3

1

事实上你有:

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) 可以通过更改来Dragon固定

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

  virtual void describe(int dummy) { std::cout << "Dragon"; }
};
于 2019-10-15T02:53:16.413 回答
0

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

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

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

于 2019-10-15T02:36:48.907 回答
0

在 C++ 中,同名但参数不同的函数是完全独立的函数,彼此没有任何关系。它们具有相同名称的事实完全无关紧要。

这与调用基类“apple”中的函数和派生类“banana”中的函数完全相同。由于基类中没有“香蕉”函数,因此您显然不能在基类中调用它。派生类中的香蕉函数显然不会覆盖基类中的函数。

我也知道在派生类中重新定义函数名会隐藏基类中的所有同名函数。

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

于 2019-10-15T02:36:59.213 回答