“override”关键字只是对被覆盖的虚拟方法的检查吗?

Is the 'override' keyword just a check for a overridden virtual method?

提问人:aiao 提问时间:12/14/2012 最后编辑:sbiaiao 更新时间:11/2/2021 访问量:56822

问:

据我了解,在 C++11 中引入关键字只不过是检查以确保正在实现的函数是基类中函数的 ing。overrideoverridevirtual

是这样吗?

C ++11 重写 虚拟函数 C++-FAQ

评论

62赞 R. Martinho Fernandes 12/14/2012
是的。
15赞 Nikos C. 12/14/2012
不过,这不是双重检查。这是唯一的检查。
18赞 KAlO2 1/6/2014
嘿,覆盖不是一个关键词,它是一种语法糖。int 覆盖=42;还行
7赞 CinchBlue 1/29/2015
所以,呃......C++11 什么时候会成为标准,以至于他们在我当地的 4 年就开始教授这样的东西?他们什么时候知道?!
3赞 underscore_d 7/3/2016
@SasQ 当然,作为常规标识符不能与说明符冲突,因为后者只能出现在一个位置,而前者在语法上永远无效。这正是他们以这种方式设置它的原因,以便在它们重要的单一上下文中启用并具有特殊含义,同时又不会剥夺用户以前将它们用作普通标识符的能力。overrideoverrideoverridefinal

答:

6赞 RonaldBarzell 12/14/2012 #1

是的,就是这样。这是一项检查,以确保人们不会尝试覆盖并通过拙劣的签名将其搞砸。这里有一个 Wiki 页面,详细解释了这一点,并有一个简短的说明性示例:

http://en.wikipedia.org/wiki/C%2B%2B11#Explicit_overrides_and_final

41赞 user1284631 12/14/2012 #2

维基百科引用:

重写特殊标识符意味着编译器将检查基类,以查看是否存在具有此确切签名的虚函数。如果没有,编译器将出错。

http://en.wikipedia.org/wiki/C%2B%2B11#Explicit_overrides_and_final

编辑(试图改进一点答案):

将方法声明为“重写”意味着该方法旨在重写基类上的(虚拟)方法。重写方法必须与它打算重写的方法具有相同的签名(至少对于输入参数而言)。

为什么有必要这样做?好吧,可以防止以下两种常见的错误情况:

  1. 一个在新方法中错误地键入了类型。编译器不知道它打算编写以前的方法,只是将其作为新方法添加到类中。问题是旧方法仍然存在,新方法只是作为重载添加的。在这种情况下,对旧方法的所有调用都将像以前一样运行,行为没有任何变化(这将是重写的真正目的)。

  2. 忘记将超类中的方法声明为“虚拟”,但仍然尝试在子类中重写它。虽然这显然会被接受,但行为不会完全符合预期:该方法不是虚拟的,因此通过指向超类的指针进行访问将结束调用旧的(超类)方法而不是新的(子类)方法。

添加“override”显然可以消除歧义:通过这一点,人们可以告诉编译器有三件事是预期的:

  1. 超类中有一个同名的方法
  2. 超类中的此方法被声明为“虚拟”(这意味着,要重写)
  3. 超类中的方法与子类中的方法(重写方法)具有相同的 (input*) 签名

如果其中任何一个为 false,则发出错误信号。

* 注意:输出参数有时具有不同但相关的类型。如果有兴趣,请阅读协变变换和逆变变换。

304赞 Kerrek SB 12/14/2012 #3

这确实是这个想法。关键是你要明确你的意思,这样就可以诊断出一个原本无声的错误:

struct Base
{
    virtual int foo() const;
};

struct Derived : Base
{
    virtual int foo()   // whoops!
    {
       // ...
    }
};

上面的代码可以编译,但不是你的意思(注意缺少)。如果你说,那么你会得到一个编译器错误,你的函数实际上没有覆盖任何东西。constvirtual int foo() override

评论

81赞 Lightness Races in Orbit 8/13/2013
+1:虽然,不幸的是,当人们建议新功能“修复”这个问题时,这有点红鲱鱼;你必须记住使用它,就像你应该记得写overrideconst ;)
1赞 Christian Rau 8/13/2013
@aschepler 类定义会有什么作用?根本没有听说过。explicit
21赞 legends2k 10/20/2013
@LightnessRacesinOrbit:是的,这不是万无一失的;然而,记住一般规则(当一个人打算写的时候疯狂地写)比记住极端情况更有可能,即复制不同原型的函数没有普遍性,只有不规则性,如缺失或写代替,等等。overrideconstcharint
2赞 iammilind 10/8/2016
@Light,这个答案中提到了说明符的最佳用例,它更像是未来主义的,而不是直接的。答案是,保持该方法。将来,当有人错误地更改签名时,它的用处就会发挥作用。overrideoverridevirtual
2赞 parsley72 5/24/2017
核心准则 C.128 指出“虚拟功能应准确指定虚拟、覆盖或最终功能之一。github.com/isocpp/CppCoreGuidelines/blob/master/......
39赞 user3792211 3/27/2015 #4

当有人更新基类虚拟方法签名(例如添加可选参数但忘记更新派生类方法签名)时,发现“覆盖”很有用。在这种情况下,基类和派生类之间的方法不再是多态关系。如果没有覆盖声明,就很难发现这种错误。

评论

1赞 Disillusioned 1/22/2017
+1.虽然是发现此类问题的好方法,但良好的单元测试覆盖率也应该有所帮助。override
1赞 Wolf 10/4/2017
这正是我对这个新说明符如此兴奋的原因。唯一的问题是,必须已经应用此功能,以防止基类中的更改导致错误。;-)
4赞 Ciro Santilli OurBigBook.com 2/5/2019 #5

C++17 标准草案

在查看了 C++17 N4659 标准草案的所有命中后,我能找到的唯一标识符参考是:overrideoverride

5 如果一个虚函数被标记为 virt-specifier 覆盖,并且没有覆盖 基类时,程序格式不正确。[ 示例:

struct B {
  virtual void f(int);
};

struct D : B {
  virtual void f(long) override; // error: wrong signature overriding B::f
  virtual void f(int) override;  // OK
}

— 结束示例 ]

所以我认为可能炸毁错误的程序实际上是唯一的效果。

3赞 Francois Bertrand 10/18/2021 #6

澄清关于虚拟的一切(因为我反复遇到这个问题!

  • virtual用于基类告诉派生可以重写函数
    • 无需在派生类中使用 virtual。如果函数具有相同的名称/参数类型 list/cv-qual/ref-qual,则会自动正确使用。
    • (实际上,在派生类中使用可能会产生微妙的错误,见下文)virtual
  • override派生类可选说明符,用于捕获错误和文档代码:
    • 告诉编译器:“确保有一个我正在重写的确切虚函数”
      • 避免错误地创建不同的函数签名,从而导致细微的错误(即 2 个应相同的略有不同的函数)
      • 告诉编码人员这是在覆盖虚拟函数

所以给定:

class base
{
public:
    virtual int foo(float x);
};

以下是一些不同的覆盖方式:

// AUTOMATIC virtual function (matches original, no keywords specified)
int foo(float x) { ; } 

// Re-specifying "virtual" uselessly (+ see pitfalls below)
virtual int foo(float x) { ; } 

// Potential issues: it is unknown if the author intended this to be a 
//    virtual function or not. Also, if the author DID intend a match but 
//    made a mistake (e.g. use "int" for the parameter), this will create
//    a subtle bug where the wrong function is called with no warning anywhere:

int foo(int x) { ; }         // SUBTLE, SILENT BUG! int instead of float param
virtual int foo(int x) { ; } // SUBTLE, SILENT BUG! int instead of float param


// Better approach: use the 'override' identifier to 
//    make sure the signature matches the original virtual function,
//    and documents programmer intent.

int foo(float x) override { ; }        // Compiler checks OK + tells coder this is virtual
int foo(int x)  override { ; }         // COMPILE ERROR, caught subtle bug
virtual int foo(int x)  override { ; } // COMPILE ERROR, caught subtle bug
                                       // (and redundant use of "virtual")

最后 (!),可以出于相同的原因使用说明符来代替,但如果您不希望在派生类中进一步重写finaloverride