是'覆盖'关键字只是一个覆盖虚方法检查?

据我所知,在c++ 11中引入override关键字不过是检查,以确保正在实现的函数是基类中virtual函数的overrideing。

就是这样吗?

53255 次浏览

是的,是这样。这是一种检查,以确保有人不会试图重写,并通过拙劣的签名把事情搞砸。这里有一个Wiki页面详细解释了这一点,并有一个简短的说明性示例:

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

维基百科:

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

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

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

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

为什么这是必要的?好了,下面两种常见的错误情况都避免了:

  1. 在新方法中键入错误的类型。编译器不知道它要编写以前的方法,只是将它作为一个新方法添加到类中。问题是旧的方法仍然存在,新方法只是作为重载添加。在这种情况下,所有对旧方法的调用都将像以前一样发挥作用,而不改变行为(这正是重写的目的)。

  2. 有人忘记将超类中的方法声明为“virtual”,但仍然试图在子类中重写它。虽然这显然是可以接受的,但行为并不完全像预期的那样:方法不是虚的,因此通过指向超类的指针访问将最终调用旧的(超类')方法而不是新的(子类')方法。

添加"override"清楚地消除了歧义:通过这个,一个是告诉编译器需要做三件事:

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

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

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

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

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


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

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

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

c++ 17标准草案

在浏览了c++ 17 N4659标准草案上的所有override命中之后,我能找到的唯一关于override标识符的引用是:

类的成员函数,如果虚函数被标记为virt-说明符override,则不重写 基类时,程序是病态形式的。(例子:< / p >
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
}

- end示例]

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

为了澄清关于虚拟的一切(因为我已经多次遇到这个问题!)

  • virtual用于基类告诉派生类一个函数可以被重写
    • 不需要在派生类中使用virtual。如果一个函数具有相同的名称/参数类型list/cv-qual/ref-qual,它将自动正确使用。
    • (实际上,在派生类中使用virtual会产生微妙的错误,见下文)
  • override派生类的一个可选说明符,用于捕捉错误&文档的代码:
    • 告诉编译器:“确保有一个我正在重写的EXACT虚函数”;
      • 避免错误地创建一个不同的函数签名,这会导致一个微妙的错误(即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")

最后(!),出于同样的原因,可以使用final说明符来代替override,但在需要在派生类中不再重写的情况下。