棘手的多态性和虚函数

Tricky Polymorphism and virtual functions

本文关键字:函数 多态性      更新时间:2023-10-16

我有以下代码:

#include <iostream> 
using namespace std; 
class K { 
public: 
    virtual void add_st(K* n) {
      cout << "add_st (K*) from Kn";
    } 
};
class L: public K { 
public: 
    virtual void add_st(L* a) {
      cout << "add_st (L*) from Ln";
    } 
}; 
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 Kn";
    } 
};
class L: public K { 
public: 
    virtual void add_st() {
      cout << "add_st (L*) from Ln";
    } 
}; 
int main() { 
    L ob, ob2;
    K  k, *pl = &ob; 
    pl->add_st(); 
    return 0; 
} 

将打印

add_st (L*) from L

为什么?

虚函数在实参列表中是不变的,在返回类型中是协变的

考虑这个问题的一个基本方法是,在基类中引入虚成员函数的地方,它定义了一个契约。

例如,给定

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的对象,只能接受类型为KN的对象。

struct P : K
{ 
    virtual K* add_st(void* a);
};

尽管从类型理论的角度来看,逆变参数是兼容的,但事实是c++支持多重继承,而且向上转换有时需要调整指针,因此逆变参数类型在实现级别就会中断。

它们将创建一个新函数(v表中的新槽),该函数将重载并隐藏现有函数,而不是覆盖它。(正如John Smith在他的回答中所说,可以使用using-declaration来避免隐藏基本版本)

下面是一个错误,因为签名是相同的,但返回类型是不兼容的:

struct Q : K
{ 
    virtual void* add_st(K* a);
};

这里的结果可以是任何对象类型,但这还不够好,契约需要一个类型为K的对象。它不能覆盖现有的函数,因为参数没有不同。所以它被拒绝了。

有关方差的更多细节,您可能需要阅读有关Liskov替换原理的内容。

首先,函数签名包括函数名及其参数类型。在第一个示例中,函数名称相同,但其参数类型不同。因此它们有不同的签名。因此,在第一个示例中,子类中的函数没有覆盖其父类中的函数。

其次还有overload和名称隐藏的概念。在您的例子中,第一个示例中的函数定义隐藏了它的父函数。如果将父函数引入到同一作用域,则子函数将重载父函数,如下所示

class L: public K { 
public: 
    using K::add_st;
    virtual void add_st() {
        cout << "add_st (L*) from Ln";
};