首页 文章

为什么派生类中的重写函数会隐藏基类的其他重载?

提问于
浏览
197

考虑一下代码:

#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
test.cpp: In function `int main()':
test.cpp:31: error: no matching function for call to `Derived::gogo(int)'
test.cpp:21: note: candidates are: virtual void Derived::gogo(int*) 
test.cpp:33:2: warning: no newline at end of file
>Exit code: 1

这里,Derived类的函数使基类中所有相同名称(不是签名)的函数不可用 . 不知何故,C的这种行为看起来不太好 . 不是多态的 .

4 回答

  • 13

    名称解析规则表明名称查找在找到匹配名称的第一个作用域中停止 . 此时,重载决策规则启动以找到可用功能的最佳匹配 .

    在这种情况下,在Derived类作用域中找到 gogo(int*) (单独),并且由于没有从int到int *的标准转换,查找失败 .

    解决方案是通过Derived类中的using声明来引入Base声明:

    using Base::gogo;
    

    ...将允许名称查找规则查找所有候选项,因此重载决策将按预期进行 .

  • 43

    这是“按设计” . 在C重载中,此类方法的解析如下所示 .

    • 从引用类型开始,然后转到基类型,找到第一个具有名为"gogo"的方法的类型

    • 仅考虑该类型上名为"gogo"的方法找到匹配的重载

    由于Derived没有名为“gogo”的匹配函数,因此重载解析失败 .

  • 375

    根据你问题的措辞来判断(你使用了"hide"这个词),你已经知道这里发生了什么 . 这种现象称为"name hiding" . 出于某种原因,每当有人问一个关于为什么名字隐藏发生的问题时,回答的人要么说这叫做"name hiding"并解释它是如何工作的(你可能已经知道),或解释如何覆盖它(你从未问过这个问题) ),但似乎没有人愿意解决实际问题.230769_问题 .

    这个决定,隐藏名称背后的理由,即它实际上被设计为C的原因,是为了避免某些违反直觉,无法预料和潜在危险的行为,如果允许继承的一组重载函数与当前的给定类中的重载 . 您可能知道在C重载决策中,通过从候选集中选择最佳函数来工作 . 这是通过将参数类型与参数类型相匹配来完成的 . 匹配规则有时可能很复杂,并且经常导致可能被无准备的用户视为不合逻辑的结果 . 向一组先前存在的函数添加新函数可能会导致重载决策结果发生相当大的变化 .

    例如,假设基类 B 有一个成员函数 foo ,它带有 void * 类型的参数,所有对 foo(NULL) 的调用都被解析为 B::foo(void *) . 让's say there'没有名字隐藏,这个 B::foo(void *) 在从 B 降序的许多不同类中可见 . 但是,假设在类 B 的某个[间接,远程]后代 D 中定义了一个函数 foo(int) . 现在,没有名称隐藏 D 同时显示 foo(void *)foo(int) 并参与重载解析 . 如果通过 D 类型的对象进行 foo(NULL) 的调用,将使用哪个函数?它们将解析为 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的状态将被部分更新,导致切片 .

    在设计语言时,这种行为被认为是不合需要的 . 作为一种更好的方法,决定遵循“名称隐藏”规范,这意味着每个类都以它声明的每个方法名称的“干净工作表”开头 . 为了覆盖此行为,用户需要显式操作:最初是继承方法的重新声明(当前已弃用),现在显式使用using-declaration .

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

  • 2

    名称隐藏是有意义的,因为它可以防止名称解析中的歧义 .

    考虑以下代码:

    class Base
    {
    public:
        void func (float x) { ... }
    }
    
    class Derived: public Base
    {
    public:
        void func (double x) { ... }
    }
    
    Derived dobj;
    

    如果在Derived中 Derived::func(double) 没有隐藏 Base::func(float) ,我们会在调用 dobj.func(0.f) 时调用基类函数,即使浮点数可以提升为double .

    参考:http://bastian.rieck.ru/blog/posts/2016/name_hiding_cxx/

相关问题