考虑下面的代码:

#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++的这种行为看起来并不好。不是多态。


当前回答

从你问题的措辞来看(你用了“隐藏”这个词),你已经知道这里发生了什么。这种现象被称为“名字隐藏”。出于某种原因,每当有人问为什么会发生名称隐藏的问题时,回答的人要么说这叫“名称隐藏”,并解释它是如何工作的(你可能已经知道了),要么解释如何覆盖它(你从来没有问过),但似乎没有人关心解决真正的“为什么”问题。

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.

另一个例子是在《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++中,这类方法的重载解析如下所示。

从引用类型开始然后到基类型,找到第一个类型它的方法名为gogo 只考虑在该类型上命名为“gogo”的方法查找匹配的重载

由于Derived没有一个名为“gogo”的匹配函数,重载解析失败。

名称解析规则表示名称查找在找到匹配名称的第一个作用域内停止。这时,重载解析规则开始发挥作用,以找到可用函数的最佳匹配。

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

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

using Base::gogo;

...将允许名称查找规则找到所有候选对象,因此重载解析将按预期进行。

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

考虑下面的代码:

class Base
{
public:
    void func (float x) { ... }
}

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

Derived dobj;

如果在Derived中Base::func(float)没有被Derived::func(double)隐藏,则在调用dobj.func(0.f)时调用基类函数,即使float可以升格为double。

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

从你问题的措辞来看(你用了“隐藏”这个词),你已经知道这里发生了什么。这种现象被称为“名字隐藏”。出于某种原因,每当有人问为什么会发生名称隐藏的问题时,回答的人要么说这叫“名称隐藏”,并解释它是如何工作的(你可能已经知道了),要么解释如何覆盖它(你从来没有问过),但似乎没有人关心解决真正的“为什么”问题。

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.

另一个例子是在《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关系。这是事实,但显然当时的人们认为,最终隐藏名字会被证明是一个较轻的邪恶。