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

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

问题描述

考虑代码:

#include <stdio.h>类基{上市:虚空gogo(int a){printf(" Base :: gogo (int) 
");};虚拟 void gogo(int* a){printf(" Base :: gogo (int*) 
");};};派生类:公共基础{上市:虚拟 void gogo(int* a){printf("派生的 :: gogo (int*) 
");};};诠释主要(){派生对象;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 派生的许多不同类中可见.但是,假设在类 B 的某些 [indirect, remote] 后代 D 中定义了一个函数 foo(int).现在,在没有名称隐藏的情况下,D 使 foo(void *)foo(int) 可见并参与重载决议.如果通过 D 类型的对象调用 foo(NULL) 将解析到哪个函数?它们将解析为 D::foo(int),因为 int 比任何指针类型更适合整数零(即 NULL).因此,在整个层次结构中,对 foo(NULL) 的调用会解析为一个函数,而在 D (及以下)中它们会突然解析为另一个函数.

The Design and Evolution of C++,第 77 页给出了另一个例子:

类基{诠释 x;上市:虚拟 void 副本(Base* p){ x = p->X;}};派生类:公共基础{诠释xx;上市:虚拟无效副本(派生* p){ xx = p-> xx;基地::复制(p);}};void f(基数 a,派生 b){a.copy(&b);//ok: 复制 b 的 Base 部分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天全站免登陆