我正在试验这段代码
class Base
{
public:
virtual void foo(int) {
// void foo(int) {
cout << "int" << endl;
}
};
class Derived : public Base
{
public:
void foo(double) {
cout << "double" << endl;
}
};
int main()
{
Base* p = new Derived;
p->foo(2.1);
Derived d;
d.foo(2); // why isn't this double?
return 0;
}
也可以在这里的在线编辑器https://onlinegdb.com/s8NwhfG_Yy
我得到
int
double
我不明白为什么d.foo(2)调用双版本。由于派生继承了int方法,并有自己的双方法,多态性不会规定2与父类匹配吗?谢谢。
调用虚方法时的多态性与符号和重载解析是正交的。前者发生在运行时,其余发生在编译时。
object->foo()
总是在编译时解析符号-重载operator()
或方法的成员变量。virtual
只是延迟选择"主体";方法的。签名总是固定的,当然也包括返回值。否则类型系统就会中断。这就是为什么不能有虚函数模板的原因之一。
你实际经历的是名称隐藏和重载解析。
对于Base* p; p->foo(2.1)
,可能的候选符号列表只有Base::foo(int)
。编译器无法知道p
指向Derived
(通常),因为必须在编译时进行选择。由于int
可以隐式转换为double
,因此选择foo(int)
。因为这个方法是虚方法,如果Derived
提供了自己的foo(int) override
,那么在运行时就会调用它而不是Base::foo(int)
。
对于Derived*d; d->foo(2)
,编译器首先在Derived
中查找符号foo
。因为存在foo(double)
,它作为foo(2)
是有效的,所以选择它。如果没有有效的候选,那么编译器才会查找基类。如果有Derived::foo(int)
,可能是虚的,编译器会选择这个,因为它是一个更好的匹配。
您可以通过写入
来禁用名称隐藏class Derived: public Base{
using Base::foo;
};
它将所有Base::foo
方法注入Derived
作用域。在此之后,Base::foo(int)
(现在真正的Derived::foo(int)
)被选为更好的匹配。
读取过载解析。答案在Details:
如果任何候选函数是成员函数(静态或非静态),但不是构造函数,则将其视为具有一个额外的形参(隐式对象形参),该形参表示调用它们的对象,并且出现在第一个实际形参之前。
方法void foo(int)
和void foo(double)
之间的选择就像函数void foo(Base, int)
和void foo(Derived, double)
之间的选择一样。第二个函数在调用d.foo(2)
时排名更高。
如果在Derived
中添加using Base::foo;
,则会在void foo(Base, int)
、void foo(Derived, int)
、void foo(Derived, double)
三个函数之间进行解析,并打印int
。