理解简单的C++继承

17

我有点难以理解为什么这段代码片段不能编译。

#include <cstdio>

class A {
public:
    virtual int potential()=0;
    virtual int potential(int arg, int arg2)=0;
};

class B : public A {
public:
    int potential() { return 1; }
    virtual int potential(int arg, int arg2) { return 2; }
};

class C : public B {
public:
    int potential(int arg, int arg2) { return 3; }
};


int main(int argc, char** argv) {
    C c;
    int value = c.potential();
    printf("Got %i\n", value);
    return 0;
}

我有两个纯虚方法,在抽象超类A中都命名为potential。然后子类B定义了这两个方法,但进一步的子类C只需要重新定义其中一个方法。

然而,在编译时,只有在C中定义的方法被识别,而potential()没有被看到(这应该已经从B继承):

In function 'int main(int, char**)':
Line 23: error: no matching function for call to 'C::potential()'
compilation terminated due to -Wfatal-errors.
如果我将整个继承树中的 A::potential(int, int) 重命名为其他名称,例如 A::somethingElse(int, int),则代码可以成功编译,并且输出结果为 Got 1,符合预期。这已经通过 clangg++ 和 MSVC 的 cl 进行了验证。对此有什么想法吗?

使用建议的 <cstdio> - Dan
2个回答

27
然而,在编译时,只有在C中定义的方法被识别出来了,而potential()并没有被识别出来(它应该是从B继承来的)。
C++工作方式不是这样的:因为你在C中实现了一个不同参数的potential方法(与名称相同但参数不同的方法),所以对于C来说,另一个方法被隐藏了。
隐藏发生是因为C ++解析(重载)方法名称的方式:当你在类的实例(这里是c)上调用一个名为potential的方法时,C ++会搜索类中是否存在该名称的方法。如果没有找到,它会继续在基类中搜索,一直向上搜索直到找到至少一个该名称的方法。
但在你的情况下,C ++不必搜索太远:该方法已经存在于C中,因此它停止了搜索。现在C ++尝试匹配方法签名。不幸的是,方法签名不匹配,但此时为时已晚:重载解析失败;C ++不会搜索其他可能匹配的方法。
有三种解决方案:
1. 在C中使用using导入:
class C : public B {
public:
    using B::potential;
    int potential(int arg, int arg2) { return 3; }
};
  • main函数中,通过一个基类实例调用该方法:

  • C c;
    B& b = c;
    int value = b.potential();
    
  • main中明确指定名称:

  • C c;
    int value = c.B::potential();
    

    有趣 - 我以前没有看到过 using 关键字被这样使用。为什么另一个方法被隐藏了?它的签名与定义的方法不同,对吧? - Dan
    @Dan:这就是C++中继承的方式。如果你愿意,可以称之为怪癖。 - Lightness Races in Orbit
    @Dan 我已经添加了关于重载解析和隐藏的说明。 - Konrad Rudolph
    这就是为什么我喜欢C++ =) 它的怪癖永远不会结束。 - Dan
    三种确实很好的解决方法,第三种方法对我也是新的 :) 但我觉得我会选择第一种。举个例子,如果我有三种不同的“潜在”方法,只有一个覆盖,那么using B::potential;是否会将B中的所有方法都引入到C的作用域中? - Dan
    @Dan 是的,using 将该名称的所有方法引入作用域。而且你是对的,这通常是最好的选择。 - Konrad Rudolph

    11

    问题出在名称隐藏。

    函数重载和函数继承并不是最好的朋友。通常你要么 [嗯,“要么”怎么翻译?]:

    • 在单个类内重载一个函数,一切正常。
    • 从基类继承一个未经重载的函数,一切正常。
    • 在派生类C中重新实现基类B中一个未经重载的函数B::func。因为有相同的名称,C::func隐藏B::func

    你正在使用继承重载,并且你的C::func正在隐藏B::func以及其每个重载,即使它们没有在C中被重新实现。

    C++中有一点奇特的混淆,但很容易解决。

    简而言之,解决方案是使用using语句将B::potential()引入C的范围内:

    class C : public B {
    public:
        using B::potential; // brings overloads from B into scope
        int potential(int arg, int arg2) { return 3; }
    };
    

    我已经写了一篇文章在这里深入探讨了这个问题。


    1
    你可以使用“either”来表示三个选项,同样也可以 :) - Dan
    1
    @丹:那不是它的工作方式,但我不会抱怨!重要的是你是否得到了答案。 - Lightness Races in Orbit
    @davka:我不认为《美国传统英语词典》是英语权威。 - Lightness Races in Orbit
    @Tomalak:碰到你了:) 这支持了你的观点,尽管如此。 - davka

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接