C++ 中的名称隐藏与 Java

Name Hiding in C++ vs Java

前几天我学到了一些关于 C++ 的新知识;以下不起作用:

class ParentClass {
public: 
    void someFunction() { printf("ParentClass::someFunction()"); }
};

class ChildClass : public ParentClass {    
public:
    void someFunction(int a) { printf("ChildClass::someFunction(int)"); }
};

int main() {
    ChildClass childClass;
    // This call is a compiler error.  
    // I would expect it to call ParentClass::someFunction()
    childClass.someFunction();
}

然而,在 Java(以及其他语言)中做完全相同的事情就像我期望的那样:

public class ParentClass {
    public void someFunction() { System.out.println("ParentClass"); }
}

public class ChildClass extends ParentClass {
    public void someFunction(int a) { System.out.println("ChildClass"); }
}

public class Main {
    public static void main(String[] args) {
        ChildClass childClass = new ChildClass();
        // The following prints "ParentClass"
        childClass.someFunction();
    }
}

那么 C++ 有什么用呢?为什么这会隐藏名称而不是重载它?

在 C++ 中,当基 class 中的一个函数与派生 class 中的一个函数同名时,就会发生名称隐藏。原因是函数调用过程的阶段。

在C++中,函数调用过程的阶段如下

  • 姓名查询
  • 过载分辨率
  • 访问控制

一旦在派生 class ChildClass 中找到名称,名称查找就会停止查找其他名称。因此,ChildClass::someFunction() 隐藏了 ParentClass.

中任何名称为 someFunction 的函数

名称查找过程后,重载解析失败,因为 ChildClass 中没有 someFunction()

核心区别在于,在 C++ 中,方法签名本质上只是方法名称,而在 Java 中,它是方法名称 其参数。

在您的情况下,通过使用相同的方法名称,您将覆盖父方法,因此不带参数的父方法不再可用。在 Java 中,您必须使用 both 具有相同名称 and 具有相同参数的方法来覆盖方法,因此在您的如果它们仍然可用。

关于 return 类型是否也应该包括在内的争论完全不同——我们不去那里。

如果您要问规则是什么,那么只要在一个范围内发现一个或多个重载,名称查找就会停止,并且不会查看任何更广泛的范围。因此,在您的情况下,someFunction 的搜索从 ChildClass 的范围开始,找到一个匹配项,然后停止。

只考虑名称,不考虑其用法(例如函数调用中的参数数量)、可访问性或其他任何内容。如果 none 个重载可用,搜索仍然不会继续到其他范围,并且程序格式错误。

如果您要问为什么规则是这样的,请考虑最初只有一个函数的情况:

struct Base {};
struct Derived : Base {void f(int);}

有人用不完全匹配的类型来称呼它

Derived d;
d.f(42.0);  // OK: double converts to int

现在假设某个对 Derived 一无所知的人决定 Base 可以使用另一个函数:

struct Base {
    void f(double);  // Completely unrelated to D::f
};

根据 C++ 规则,该函数将被使用 D::f 的代码忽略,它将继续像以前一样工作。如果新函数被认为是一个重载,它会是一个更好的匹配,并且使用 D::f 的代码会突然改变行为,可能会导致令人头疼和冗长的调试会话。

如果您希望将派生 class 范围内的所有基 class 函数都包含在被视为重载的范围内,那么 using 声明就可以做到这一点。在你的情况下:

using ParentClass::someFunction;

或者,为了避免上述情况,但代价是一些乏味的措辞,您可以为您想要的特定重载编写转发函数:

void someFunction() {ParentClass::someFunction();}