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

标签 c++ polymorphism overriding

考虑代码:

#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

在这里,派生类的函数正在超越基类中所有同名(非签名)的函数。不知何故,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 页给出了另一个例子:

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*)
}

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

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

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

关于c++ - 为什么派生类中的重写函数会隐藏基类的其他重载?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1628768/

相关文章:

c++ - 重载 == 运算符导致丢弃限定符错误

c++ - 如何使用 Objective-C++ 从 Objective-C Cocoa 接口(interface)调用 C++ 方法

c++ - 在C++中模拟虚拟构造函数

c++ - 用模板函数覆盖虚函数

vb.net - 如何覆盖整数、字符串等基本类型?

python - 通过猴子修补覆盖方法

java - Java 中的数据类型可以重写吗?

c++ - 我可以用 constexpr 函数声明一个静态数组吗

c++ - 在可变模板类中使用可变参数的显式特化 [MSVS '12: Nov. ' 12 CTP : error C3522]

java - 如果没有 super 类, super 构造函数?