(在msvc2017上测试(
struct AAA
{
virtual float run(int arg)
{
return 5.5f;
}
};
struct BBB : AAA
{
virtual bool run(double arg)
{
return false;
}
};
struct CCC : BBB
{
virtual float run(int arg)
{
return 7.7f;
}
virtual bool run(double arg)
{
return true;
}
};
CCC c;
BBB* pb = &c;
pb->run(5); // call CCC::run(double arg), WHY??
pb->run((int)5); // call CCC::run(double arg), WHY??
为什么pb->run(5)
只叫CCC::run(double arg)
,而不叫CCC::run(int arg)
?
具有不同签名的子类的虚拟方法是否与基类的接口重叠?
一切都很简单。
类 BBB 实际上有两个虚函数。一个在其基类 AAA 中声明
struct AAA
{
virtual float run(int arg)
{
return 5.5f;
}
};
其他在类 BBB 本身中声明。
struct BBB : AAA
{
virtual bool run(double arg)
{
return false;
}
};
在类 BBB 中声明的函数隐藏在类 AAA 中声明的函数。(在派生类中声明的任何名称都会隐藏在派生类的基类中声明的具有相同名称的实体(
在类 CCC 中,这两个函数都被覆盖。
这些函数调用
pb->run(5); // call CCC::run(double arg), WHY??
pb->run((int)5); // call CCC::run(double arg), WHY??
没有区别,因为它们的参数的类型为int
.
指针pb
的静态类型为BBB *
。因此,编译器搜索在类 BBB 中运行的名称。
在类中,只有一个具有此名称的函数可见。它是在类中声明的函数
virtual bool run(double arg)
{
return false;
}
因此,编译器使用此签名运行此虚函数,但使用为类 CCC 定义的虚函数指针表调用它,因为指针pb
的动态类型为CCC *
。
您可以通过using
声明使在类 AAA 中声明的函数在类 BBB 中可见。例如
struct BBB : AAA
{
using AAA:: run;
virtual bool run(double arg)
{
return false;
}
};
在这种情况下,函数的声明(在类 AAA 中声明(也将是类 BBB 内的成员声明。也就是说,类 BBB 将具有两个重载的不同虚函数的声明。
这是一个演示程序
#include <iostream>
struct AAA
{
virtual float run(int arg)
{
return 5.5f;
}
};
struct BBB : AAA
{
using AAA:: run;
virtual bool run(double arg)
{
return false;
}
};
struct CCC : BBB
{
virtual float run(int arg)
{
return 7.7f;
}
virtual bool run(double arg)
{
return true;
}
};
int main()
{
CCC c;
BBB* pb = &c;
std::cout << pb->run(5) << 'n';
std::cout << pb->run(5.6 ) << 'n';
return 0;
}
它的输出是
7.7
1
为了使派生类及其基类中的成员声明的情况更加清晰,请考虑块作用域的类似情况。
这是一个演示程序
#include <iostream>
void f( int ) { std::cout << "void f( int )n"; }
void f( double ) { std::cout << "void f( double )n"; }
int main()
{
void f( double );
f( 5 );
f( 5.5 );
return 0;
}
函数的内部声明f
在函数main
的块作用域中,隐藏函数在全局作用域中的其他声明。
程序输出为
void f( double )
void f( double )
当你这样做时
struct BBB : AAA
{
virtual bool run(double arg)
{
return false;
}
};
run
的签名与AAA
中的run
不同。这意味着BBB::run(double)
将隐藏AAA::run(int)
. 既然如此,您可以从BBB
调用的唯一run
是bool run(double arg)
. 当你这样做时
pb->run(5);
它找到了bool BBB::run(double arg)
,因为这是唯一可以从BBB
静态调用的函数,然后虚拟调度启动调用CCC::run(double)
为了获取要调用的函数的int
版本,您需要将int
版本引入BBB
。 您可以通过编写一个来执行此操作,也可以使用using AAA::run;
将其导入。 执行其中任何一个都会使pb->run(5);
从CCC
调用run
的int
版本。
不要忘记,在玩多态性时,你应该将顶级析构函数(在本例中为AAA
(声明为虚拟的。 这允许您在使用动态分配时正确删除对象。 有关完整详细信息,请参阅:何时使用虚拟析构函数?