我有以下代码。
#include <iostream>
using namespace std;
class K {
public:
virtual void add_st(K* n) {
cout << "add_st (K*) from K\n";
}
};
class L: public K {
public:
virtual void add_st(L* a) {
cout << "add_st (L*) from L\n";
}
};
int main() {
L ob, ob2;
K k, *pl = &ob;
pl->add_st(&ob2);
return 0;
}
该计划的输出将是:
add_st (K*) from K
如果我没有错过任何东西的原因是虚拟功能表。该对象从层次结构的顶部向下生成到最低的类。
但是这段代码:
#include <iostream>
using namespace std;
class K {
public:
virtual void add_st() {
cout << "add_st (K*) from K\n";
}
};
class L: public K {
public:
virtual void add_st() {
cout << "add_st (L*) from L\n";
}
};
int main() {
L ob, ob2;
K k, *pl = &ob;
pl->add_st();
return 0;
}
将打印
add_st (L*) from L
为什么?
答案 0 :(得分:6)
参数列表中的虚函数不变,返回类型上的协变。
想到这一点的基本方法是,在基类中引入虚拟成员函数的地方,它定义了一个契约。
例如,给定
struct K
{
virtual K* add_st(K* n);
};
合同是 add_st
接受K
类型的任何对象(通过指针),返回K
类型的对象(通过指针)。
这会覆盖它
struct L : K
{
virtual K* add_st(K* a);
};
因为合同明显得到满足,所以:
struct M : K
{
virtual M* add_st(K* a);
};
因为返回是M
类型的对象,通过继承也是K
类型的对象;合同得到满足。
但是(问题中的情况)不会覆盖
struct N : K
{
virtual K* add_st(N* a);
};
因为它无法接受 K
类型的任何对象,只能接受类型K
和类型N
的对象。这两个都不是:
struct P : K
{
virtual K* add_st(void* a);
};
即使从类型理论的角度来看,逆变参数也是兼容的,事实上C ++支持多重继承,而有时候需要指针调整,因此逆向参数类型在实现级别会中断。
他们将创建一个新函数(v表中的新插槽),它重载并隐藏现有函数,而不是覆盖它。 (正如约翰史密斯在他的回答中所说,可以使用 using-declaration 来避免隐藏基础版本)
以下是错误,因为签名相同,但返回类型不兼容:
struct Q : K
{
virtual void* add_st(K* a);
};
这里的结果可以是任何对象类型,但这还不够好,合同需要类型为K
的对象。它不能覆盖现有的功能,因为参数没有区别。所以它被拒绝了。
有关 variance 的详细信息,您可能需要了解Liskov Substitution Principle。
答案 1 :(得分:1)
首先,函数签名包括函数名称及其参数类型。在第一个示例中,函数名称相同,但其参数类型不同。因此他们有不同的签名。因此,在您的第一个示例中,子类中的函数未覆盖其父级中的函数。
其次,还有overload
和名称隐藏的概念。在您的情况下,第一个示例中的函数定义隐藏其父函数。如果将父函数放入同一范围,子函数将重载父函数,如此
class L: public K {
public:
using K::add_st;
virtual void add_st() {
cout << "add_st (L*) from L\n";
};