考虑代码:
#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类的某些 [indirect, remote] 后代中定义B了一个函数foo(int)。现在,没有名称隐藏D既可见foo(void *)又foo(int)参与重载决议。foo(NULL)如果通过类型的对象进行调用,将解析到哪个函数D?他们将解析为D::foo(int),因为int它更适合积分零(即NULL) 比任何指针类型。因此,在整个层次结构中调用foo(NULL)解析到一个函数,而在D(和下)它们突然解析到另一个。
B
foo
void *
foo(NULL)
B::foo(void *)
D
foo(int)
foo(void *)
D::foo(int)
int
NULL
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*) }
如果没有这条规则,b 的状态将被部分更新,从而导致切片。
在设计语言时,这种行为被认为是不可取的。作为一种更好的方法,决定遵循“名称隐藏”规范,这意味着每个类都以关于它声明的每个方法名称的“干净表”开头。为了覆盖此行为,需要用户进行显式操作:最初是重新声明继承的方法(当前已弃用),现在显式使用 using-declaration。
正如您在原始帖子中正确观察到的那样(我指的是“非多态”评论),这种行为可能被视为违反类之间的 IS-A 关系。这是真的,但显然当时决定最终隐藏名字将被证明是一种较小的邪恶。