This基本上是Herb Sutter的书Item 21. Overriding Virtual Functions
中Exceptional C++
中给出的示例的副本。
#include <iostream>
#include <complex>
using namespace std;
class Base
{
public:
virtual void f(int);
virtual void f(double);
virtual ~Base() {};
};
void Base::f(int) { cout << "Base::f(int)" << endl; }
void Base::f( double ) { cout << "Base::f(double)" << endl; }
class Derived: public Base {
public:
void f(complex<double>);
};
void Derived::f(complex<double>) { cout << "Derived::f(complex)" << endl; }
int main()
{
Base* pb = new Derived;
pb->f(1.0);
delete pb;
}
代码打印Base::f(double)
,我没有遇到任何问题。但我无法理解作者在第122页的顶部给出的解释(重点是我的):
有趣的是,即使Base * pb指向Derived 对象,这会调用Base :: f(double),因为重载决策是 在静态类型(这里是Base)上完成,而不是动态类型(这里 衍生的)
我的理解是,来电pb->f(1.0)
是虚拟来电,Base::f(double)
是f(double)
中Derived
的最后覆盖。这与函数重载有什么关系?
答案 0 :(得分:12)
这里微妙的部分是虚拟方法是调度函数调用的机制,而重载是影响调用的分辨率的功能。
也就是说,对于任何调用,编译器需要确定应该调用哪个方法(解析它);之后,在逻辑上不同的操作中,它需要生成调用该方法的正确实现的代码(发送它)。
根据上面给出的Base
和Derived
的定义,我们可以很容易地推断,如果在f(double)
上调用Base*
,那么应该将调用分派给任何派生的覆盖(如果适用)优先于基本实现。但回答这个回答的问题完全不同于
当来源说
pb->f(1.0)
时,哪个方法名为f
应该用来解决方法调用吗?
正如Sutter解释的那样,规范说当解析调用时,编译器将查看f
指向的静态类型上声明的名为pb
的方法;在这种情况下,静态类型为Base*
,因此根本不会考虑Derived
上声明的重载(不覆盖!)。但是,如果调用解析为的方法为virtual
,那么Derived
上提供的可能实现将按预期使用。
答案 1 :(得分:3)
这个例子很有意思的原因是,如果pb
是Derived*
而不是Base*
,或者编译器可能以某种方式使用动态类型而不是静态类型执行重载解析时,它会匹配pb->f(1.0)
到void Derived::f(complex<double>)
的调用(complex<double>
可以隐式构造double
)。这是因为派生类中存在名为f
的函数有效地隐藏了具有相同名称的任何基类重载,即使它们的参数列表不同。但由于pb
的静态类型实际上是Base*
,因此不会发生这种情况。
答案 2 :(得分:0)
在这个例子中,尽管virtual
重复出现,但根本没有方法覆盖;派生类中的方法f
将覆盖基类中的方法,因为参数类型不匹配。鉴于这种情况,pb->f
的调用无法调用(唯一)方法Derived::f
。重载解析/名称查找(仅考虑静态类型pb->f
的方法)必须在声明为Base::f
的两个方法之间进行选择,并且在示例中,它将选择参数类型为{{1的方法}}。 (在运行时,如果在不同的派生类中定义了一个而不是double
,则可能最终调用覆盖,并且如果修改了示例以便Derived
可能指向到另一个派生类的对象。)
另一个问题是,如果从{static}的表达式调用pb
,则不会同时考虑f
和Base
中的Derived
方法进行重载解析)这次输入f
,因为基类中的方法被Derived
中的f
声明隐藏,因此它们不适用于此类调用。我认为可以通过声明Derived
来避免这种隐藏,其中提升&#34;提升&#34;将方法using Base::f;
转换为Base::f
,就好像它们也在那里被宣布一样(但我承认不知道这个机制的细节;我认为升降机将覆盖具有相同参数类型的虚拟基础方法,但它没什么区别,因为升降机无论如何都会引用基类中的实现。)