【问题标题】:Unclear warning on overridden virtual method [duplicate]覆盖虚拟方法的不明确警告[重复]
【发布时间】:2018-04-22 22:20:32
【问题描述】:

我有一个Base 类,它提供一些业务逻辑和虚拟方法,可以进一步覆盖。另外,我想用Decorator 扩展一些从Base 继承的类。这是简化的设置:

struct Base
{
    ~Base() = default;
    virtual void foo(int) {};
    virtual void foo(double) {};
};

template<typename T>
struct Decorator : public T
{

};

struct Middle : public Decorator<Base>
{
    virtual void foo(int) override {};    
};

struct Final : public Middle
{
    virtual void foo(double) override {};    
};

当我使用 clang 和 -Wall -Wextra 编译代码时,我收到以下警告:

21 : <source>:21:18: warning: 'Final::foo' hides overloaded virtual function [-Woverloaded-virtual]
virtual void foo(double) override {};    
             ^

16 : <source>:16:18: note: hidden overloaded virtual function 'Middle::foo' declared here: type mismatch at 1st parameter ('int' vs 'double')
    virtual void foo(int) override {};    
                 ^

GCC 没有抱怨,老实说,我不知道 clang 在这里发现了什么问题。

我使用 Compiler Explorer 运行最近的 clang 和 GCC:https://godbolt.org/g/fC5XXT

【问题讨论】:

  • Middle 中添加覆盖foo(double) 的有趣之处在于解决了这个问题。因此,您可以通过在此额外覆盖中调用基类方法来解决此问题。
  • 一个非常相似的问题:stackoverflow.com/questions/6727087/…
  • 警告正确; foo(double)Final 中隐藏 foo(int) 在中间。如果您创建Final 的实例并说foo(1),它将调用double 版本。
  • Martin Bonner 的回答是正确的——这与隐藏规则有关。我还关闭了这个问题,因为它与隐藏规则阻止编译的类似情况重复。

标签: c++


【解决方案1】:

这个警告是关于名字隐藏的。如果您在作用域中声明一个名称(foo 的覆盖),它将在“外部”作用域(在本例中为基类)中隐藏该名称的 所有 声明。 Decorator 在这里无关紧要。

struct Base
{
    ~Base() = default;
    virtual void foo(int) {};
    virtual void foo(double) {};
};

struct Middle : public Base
{
    void foo(int) override {};    
};

struct Final : public Middle
{
     void foo(double) override {};    
};

int main()
{
   Final f;
   f.foo(0.0); // Calls Final::foo(double);
   f.foo(0);   // *Also* calls Final::foo(double) - because Middle::foo(int) is hidden.
   Middle& m = f;
   m.foo(0);   // Calls Middle::foo(int);
   m.foo(0.0); // *Also* calls Middle::foo(int) - because Base::foo(double) is hidden.
   Base& b = m;
   b.foo(0);   // Calls Middle::foo(int) - because that overrides Base::foo(int) and
               // the dynamic type of b is a (sub-class of) Middle.
   b.foo(0.0); // Calls Final::foo(double) - because that override Base::foo(double) and
               // the dynamic type of b is Final.
   return 0;

}

调用 m 和 f 的行为令许多人感到惊讶,因此 Clang 发出警告。您可以通过以下方式抑制它:

struct Middle : public Base
{
    using Base::foo;
    void foo(int) override {};    
};

struct Final : public Middle
{
     using Middle::foo;
     void foo(double) override {};    
};

在这种情况下,所有类都将具有 foo(int)foo(double)

【讨论】:

    【解决方案2】:

    编辑以合并 AnT 的评论:

    Clang 的行为正确,遵循 C++ 的名称隐藏规则。简短而甜蜜的描述is available here。简而言之……

    派生类的成员隐藏基类中与派生类成员同名的任何成员。

    这包括标记为virtual 的基类方法。

    原答案如下:

    在决定您尝试调用的方法时,看起来 Clang 优先考虑函数名称而不是签名。这是您的类的示例用法...

    int main(void) {
        Final f;
        f.foo(3.14159);
        f.foo(0);
    
        Middle* m = static_cast<Middle*>(&f);
        m->foo(3.14159);
        m->foo(0);
    
        Base* b = static_cast<Base*>(&f);
        b->foo(3.14159);
        b->foo(0);
    }
    

    请注意在调用站点生成的附加警告...

    32 : <source>:32:12: warning: implicit conversion from 'double' to 'int' changes value from 3.14159 to 3 [-Wliteral-conversion]
        m->foo(3.14159);
           ~~~ ^~~~~~~
    

    https://godbolt.org/g/qkjqEN

    尽管Middle 类从Base 继承了void foo(double) 方法,但Clang 似乎假设您打算调用Middle 中声明的void foo(int) override 方法。

    正如其他人所提到的,您可以添加更多覆盖来帮助 Clang 解析您打算调用的方法。 this StackOverflow question 提供了另一种解决方案,带有 using 关键字。 MiddleFinal 中的声明将变为以下...

    struct Middle : public Decorator<Base>
    {
        using Base::foo;
        void foo(int) override { std::cout << "Middle::foo" << std::endl; };
    };
    
    struct Final : public Middle
    {
        using Base::foo;
        void foo(double) override { std::cout << "Final::foo" << std::endl; };
    };
    

    https://godbolt.org/g/Qe1WMm

    【讨论】:

    • "看起来 Clang 正在优先考虑函数名...", "尽管 Middle 类继承了 Basevoid foo(double) 方法,但 Clang 似乎在假设。 ……”。您正在描述完全平庸和琐碎的 名称隐藏,这是一个众所周知的 C++ 功能,这里的任何人都不会感到惊讶。 “优先考虑”或“似乎假设”任何事情的不是 Clang。这是C++语言的一个基本特性。
    • 为了强化 AnT 所说的:当参数是双精度时,任何未能调用 Middle::foo(int) 的编译器都会被彻底破坏。
    【解决方案3】:

    那个-Woverloaded-virtual警告的初衷是为了捕捉以下情况(在override还不存在的时代)

    struct Base
    {
        virtual void foo(double) {}
    };
    
    struct Middle : Base
    {
        void foo(int) {}
    };
    

    它会生成相同的警告。编译器怀疑您在派生类的函数声明中输入了错误的参数列表。

    您在案例中观察到的情况可能是该警告的意外副作用。在现代 C++ 中,override 确保您的参数列表是正确且有意的,但编译器仍然不相信:此函数的“隐藏”重载版本的存在仍会使其发出警告。

    正如您自己指出的,GCC 以更智能的方式处理这种情况。显然在 GCC 中-Woverloaded-virtual 不是-Wall -Wextra 的一部分 - 您必须明确指定它。

    【讨论】:

    • wandbox.org/permlink/IILi2LkKeXPXlXif: 尝试取消注释using
    • 这里的问题是重载解析,而不是覆盖。警告信息对此非常清楚。
    • @Zdeněk Jelínek:没有人说问题在于覆盖。我的观点是-Woverloaded-virtual 旨在发现问题,这些问题最终被override 彻底消除。 override 确保-Woverloaded-virtual 发现的问题绝无可能。
    • @Gruffalo:所以?你想表达什么观点?您的示例演示了简单的名称隐藏(与 virtual 完全无关)。这个问题与世界本身一样古老,但-Woverloaded-virtual 与它无关。这不是-Woverloaded-virtual 曾经/打算捕捉的内容。
    • 好的,稍微改进一下示例:wandbox.org/permlink/JDn2yfZ2nqHo9NXu。 clang 对警告是正确的,因为仍然有可能犯错误。而且这个错误不是最明显的一个
    猜你喜欢
    • 2013-01-15
    • 2012-11-28
    • 2020-08-31
    • 1970-01-01
    • 2016-01-24
    • 2012-06-19
    • 1970-01-01
    • 2021-11-02
    • 1970-01-01
    相关资源
    最近更新 更多