考虑代码:

#include <stdio.h>

class Base {
public:
    virtual void gogo(int a){
        printf(" Base :: gogo (int) \n");
    };

    virtual void gogo(int* a){
        printf(" Base :: gogo (int*) \n");
    };
};

class Derived : public Base{
public:
    virtual void gogo(int* a){
        printf(" Derived :: gogo (int*) \n");
    };
};

int main(){
    Derived obj;
    obj.gogo(7);
}

得到这个错误:

> g++ -pedantic -Os test.cpp -o测试
test.cpp:在函数“int main()”中:
test.cpp:31:错误:没有匹配的函数可以调用`Derived::gogo(int)'
test.cpp:21:注意:候选者是:virtual void Derived::gogo(int *)
test.cpp:33:2:警告:文件末尾没有换行符
>退出代码:1

在这里,派生类的功能使基类中所有具有相同名称(不是签名)的功能黯然失色。不知何故,C++的这种行为看起来并不正常。不是多态的。

最佳答案

根据问题的措辞(使用“隐藏”一词)来判断,您已经知道这里发生了什么。这种现象称为“名称隐藏”。出于某种原因,每当有人问到为什么发生名称隐藏的问题时,回答的人要么说这称为“名称隐藏”,然后说明其工作原理(您可能已经知道),要么说明如何覆盖它(您知道从来没有问过),但似乎没有人关心解决实际的“为什么”问题。

该决定(即隐藏名称的根本原因,即为什么实际上将其设计为C++)是要避免某些直觉,不可预见和潜在危险的行为,如果允许将继承的重载函数集与当前函数集混合使用,可能会发生这种行为。给定类中的重载。您可能知道,在C++中,重载解析通过从候选集中选择最佳功能来起作用。这是通过将参数的类型与参数的类型匹配来完成的。匹配规则有时可能会很复杂,并且经常导致结果,准备不充分的用户可能认为这是不合逻辑的。向一组先前存在的功能中添加新功能可能会导致过载解析结果发生相当大的变化。

例如,假设基类B具有成员函数foo,该成员函数接受类型为void *的参数,并且所有对foo(NULL)的调用都被解析为B::foo(void *)。假设没有隐藏名称,并且B::foo(void *)B的许多不同类中可见。但是,假设在D类的某些[间接,远程]后代B中定义了一个函数foo(int)。现在,无需隐藏名称,D既可以看到foo(void *)也可以看到foo(int)并参与重载解析。如果通过foo(NULL)类型的对象进行,对D的调用将解析为哪个函数?它们将解析为D::foo(int),因为int比所有指针类型都更适合整数零(即NULL)。因此,在整个层次结构中,对foo(NULL)的调用都解析为一个函数,而在D(及以下)中,它们突然解析为另一个函数。

《 C++的设计和演化》第77页提供了另一个示例:

class Base {
    int x;
public:
    virtual void copy(Base* p) { x = p-> x; }
};

class Derived{
    int xx;
public:
    virtual void copy(Derived* p) { xx = p->xx; Base::copy(p); }
};

void f(Base a, Derived b)
{
    a.copy(&b); // ok: copy Base part of b
    b.copy(&a); // error: copy(Base*) is hidden by copy(Derived*)
}

没有这个规则,b的状态将被部分更新,从而导致 slice 。

在设计语言时,这种行为被认为是不希望的。作为更好的方法,决定遵循“名称隐藏”规范,这意味着相对于其声明的每个方法名称,每个类均以“干净的表”开头。为了覆盖此行为,用户需要执行显式操作:最初是对继承方法的重声明(当前不推荐使用),现在是对use-declaration的显式使用。

正如您在原始帖子中正确观察到的那样(我指的是“非多态”评论),此行为可能被视为违反类之间的IS-A Relationip。这是事实,但很显然,当时可以确定,隐藏姓名最终将被证明是一种较小的罪恶。

09-04 16:10
查看更多