为什么派生类中的重写函数隐藏了基类的其他重载? [英] Why does an overridden function in the derived class hide other overloads of the base class?

查看:34
本文介绍了为什么派生类中的重写函数隐藏了基类的其他重载?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑代码:

#include 类基{民众:虚空 gogo(int a){printf(" Base :: gogo (int) 
");};虚拟无效 gogo(int* a){printf(" Base :: gogo (int*) 
");};};派生类:公共基础{民众:虚拟无效 gogo(int* a){printf(" 派生 :: gogo (int*) 
");};};int main(){派生对象;obj.gogo(7);}

遇到这个错误:

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

这里,Derived 类的函数覆盖了基类中所有同名(不是签名)的函数.不知何故,C++ 的这种行为看起来不太好.不是多态的.

解决方案

根据您问题的措辞(您使用了隐藏"一词),您已经知道这里发生了什么.这种现象被称为姓名隐藏".出于某种原因,每次有人问起为什么姓名隐藏的问题时,回答的人要么说这称为姓名隐藏",要么就是姓名隐藏".并解释它是如何工作的(你可能已经知道),或者解释如何覆盖它(你从来没有问过),但似乎没有人关心解决实际的为什么".问题.

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

例如,假设基类 B 有一个成员函数 foo,它接受一个 void * 类型的参数,并且所有调用将 foo(NULL) 解析为 B::foo(void *).假设没有名称隐藏,并且这个 B::foo(void *) 在从 B 降序的许多不同类中都是可见的.然而,假设在类 B 的某个 [indirect, remote] 后代 D 中定义了一个函数 foo(int).现在,没有名称隐藏 D 具有 foo(void *)foo(int) 可见并参与重载解析.如果通过 D 类型的对象调用 foo(NULL) 将解析为哪个函数?它们将解析为 D::foo(int),因为 int 比任何指针类型都更能匹配整数零(即 NULL).因此,在整个层次结构中,对 foo(NULL) 的调用解析为一个函数,而在 D(及以下)中,它们突然解析为另一个函数.

C++ 的设计和演变,第 77 页中给出了另一个示例:

class Base {整数 x;民众:virtual void copy(Base* p) { x = p->X;}};派生类:公共基础{国际 xx;民众:virtual void copy(Derived* p) { xx = p->xx;基地::复制(p);}};void f(Base a, Derived b){a.copy(&b);//ok: 复制 b 的基础部分b.copy(&a);//错误:copy(Base*) 被 copy(Derived*) 隐藏}

如果没有这条规则,b 的状态将被部分更新,导致切片.

在设计语言时,这种行为被认为是不受欢迎的.作为更好的方法,决定遵循名称隐藏"原则.规范,这意味着每个类都以空白页"开头.关于它声明的每个方法名称.为了覆盖此行为,需要用户执行显式操作:最初是对继承方法的重新声明(目前已弃用),现在显式使用 using-declaration.

正如您在原始帖子中正确观察到的(我指的是非多态"评论),这种行为可能被视为违反了类之间的 IS-A 关系.这是真的,但显然当时人们决定隐藏姓名最终会证明是一种较小的邪恶.

Consider the code :

#include <stdio.h>

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

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

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

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

Got this error :

>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

Here, the Derived class's function is eclipsing all functions of same name (not signature) in the base class. Somehow, this behaviour of C++ does not look OK. Not polymorphic.

解决方案

Judging by the wording of your question (you used the word "hide"), you already know what is going on here. The phenomenon is called "name hiding". For some reason, every time someone asks a question about why name hiding happens, people who respond either say that this called "name hiding" and explain how it works (which you probably already know), or explain how to override it (which you never asked about), but nobody seems to care to address the actual "why" question.

The decision, the rationale behind the name hiding, i.e. why it actually was designed into C++, is to avoid certain counter-intuitive, unforeseen and potentially dangerous behavior that might take place if the inherited set of overloaded functions were allowed to mix with the current set of overloads in the given class. You probably know that in C++ overload resolution works by choosing the best function from the set of candidates. This is done by matching the types of arguments to the types of parameters. The matching rules could be complicated at times, and often lead to results that might be perceived as illogical by an unprepared user. Adding new functions to a set of previously existing ones might result in a rather drastic shift in overload resolution results.

For example, let's say the base class B has a member function foo that takes a parameter of type void *, and all calls to foo(NULL) are resolved to B::foo(void *). Let's say there's no name hiding and this B::foo(void *) is visible in many different classes descending from B. However, let's say in some [indirect, remote] descendant D of class B a function foo(int) is defined. Now, without name hiding D has both foo(void *) and foo(int) visible and participating in overload resolution. Which function will the calls to foo(NULL) resolve to, if made through an object of type D? They will resolve to D::foo(int), since int is a better match for integral zero (i.e. NULL) than any pointer type. So, throughout the hierarchy calls to foo(NULL) resolve to one function, while in D (and under) they suddenly resolve to another.

Another example is given in The Design and Evolution of C++, page 77:

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

class Derived : public Base{
    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*)
}

Without this rule, b's state would be partially updated, leading to slicing.

This behavior was deemed undesirable when the language was designed. As a better approach, it was decided to follow the "name hiding" specification, meaning that each class starts with a "clean sheet" with respect to each method name it declares. In order to override this behavior, an explicit action is required from the user: originally a redeclaration of inherited method(s) (currently deprecated), now an explicit use of using-declaration.

As you correctly observed in your original post (I'm referring to the "Not polymorphic" remark), this behavior might be seen as a violation of IS-A relationship between the classes. This is true, but apparently back then it was decided that in the end name hiding would prove to be a lesser evil.

这篇关于为什么派生类中的重写函数隐藏了基类的其他重载?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆