最终是否意味着覆盖?

qua*_*ant 49 c++ overriding virtual-functions final

据我所知,override关键字声明给定的声明实现了一个基本virtual方法,如果找不到匹配的基本方法,编译应该失败.

我对该final关键字的理解是它告诉编译器没有类会覆盖这个virtual函数.

那是override final多余的吗?好像编译好了.哪些信息override final传达的final不是?这种组合的用例是什么?

Col*_*mbo 43

final不要求函数首先覆盖任何东西.其效果在[class.virtual]/4中定义为

如果f某个类中的虚函数B使用virt-specifier 标记, final并且在DB函数D::f 覆盖派生的类中B::f,则该程序格式错误.

而已.现在override final只是意味着
"这个函数覆盖了一个基类one(override)并且不能自己重写(final)."
final它自己会强加一个较弱的要求. overridefinal有独立的行为.


注意,final只能用于虚函数 - [class.mem]/8

的virt说明符-SEQ应仅在一个虚拟的成员函数(10.3)的声明出现.

因此宣言

void foo() final;
Run Code Online (Sandbox Code Playgroud)

实际上是一样的

virtual void foo() final override;
Run Code Online (Sandbox Code Playgroud)

因为两者都需要foo覆盖某些东西 - 第二个声明是通过使用override,第一个声明是有效的,当且仅当它foo是隐式虚拟时,即foo覆盖foo在基类中调用的虚函数时,这会使得foo派生的虚函数自动虚拟. 因此override,在声明中final,多余的,但不会virtual发生.
尽管如此,后一种声明表达的意图更加清晰,绝对应该是首选.

  • 我喜欢你的答案,但我想澄清一点,从实际的角度来看,`virtual void f()final override`和`void f()final`在某种意义上是相同的,如果他们不这样做就会失败覆盖一些东西 `final`仅对虚函数有效,后者'f`的声明只有在覆盖函数时才是虚函数.但后者的错误消息可能不太精确. (9认同)
  • 最有可能的是,例如 [Google C++ 风格指南](https://google.github.io/styleguide/cppguide.html#Inheritance) 建议“用以下之一注释虚拟函数或虚拟析构函数的重写”: **覆盖或(不太频繁)最终说明符**。声明覆盖时不要使用 virtual”。 (3认同)
  • *后者声明表达了更清楚的意图* - 我不这么认为.你的描述让我更喜欢第一种声明风格**,原因与我在重写虚拟方法时避免添加`virtual`的原因相同:它没有增加任何价值.但它当然比编写`virtual void foo()final更好;` (2认同)
  • 我认为在您的描述之后“final”*必须*是“virtual”(我刚刚学习这些新功能),并且由于您省略了“virtual”关键字,如果它不是“override”,它就不会编译。或者也许我忽略了一个可能导致不良行为的案例?使用非压倒性的最终虚拟我只是想弄清楚下面[Angew的答案](/sf/answers/2058876151/)... (2认同)

Rei*_*ica 18

final并不一定意味着该函数被覆盖.这是完全有效的(如果有些可疑值)申报虚函数作为final首次在继承层次声明.

我可以想到创建一个虚拟的,即时的最终函数的一个原因是,如果你想阻止派生类给出相同的名称和参数不同的含义.

  • @Carlton 这取决于编译器。就标准而言,它使类具有多态性,例如,这意味着“dynamic_cast”必须为它工作。我相信虚函数的 vtable 实现也需要 vtable 来使 `dynamic_cast` 工作。 (2认同)
  • 如果*您想防止派生类赋予相同的名称和不同的含义*,请不要将其设为虚方法。我在这个模糊的描述中没有看到*可行的用例*(与@Carlton 不同),但我真的很感兴趣。 (2认同)

tcb*_*tcb 5

以下代码(带有final说明符)可编译。但final替换为时编译失败override final。因此override final传达的信息比仅仅 . 更多的信息(并阻止编译)final

class Base
{
public:
    virtual ~Base() {}
};

class Derived : public Base
{
public:
    virtual void foo() final
    {
        std::cout << "in Derived foo\n";
    }
};
Run Code Online (Sandbox Code Playgroud)

本质上,override final表示此方法不能在任何派生类中重写,并且此方法重写基类中的虚拟方法。final单独不指定基类重写部分。


Jae*_*ege 5

(如果要赶时间,请跳到最后查看结论。)

双方overridefinal只能申报一个虚函数出现。这两个关键字可以在同一函数声明中使用,但同时使用它们是否有用取决于情况。

以以下代码为例:

#include <iostream>
using std::cout; using std::endl;

struct B {
  virtual void f1() { cout << "B::f1() "; }
  virtual void f2() { cout << "B::f2() "; }
  virtual void f3() { cout << "B::f3() "; }
  virtual void f6() final { cout << "B::f6() "; }
  void f7() { cout << "B::f7() "; }
  void f8() { cout << "B::f8() "; }
  void f9() { cout << "B::f9() "; }
};

struct D : B {
  void f1() override { cout << "D::f1() "; }
  void f2() final { cout << "D::f2() "; }
  void f3() override final { cout << "D::f3() "; }  // need not have override
  // should have override, otherwise add new virtual function
  virtual void f4() final { cout << "D::f4() "; }
  //virtual void f5() override final;  // Error, no virtual function in base class
  //void f6(); // Error, override a final virtual function
  void f7() { cout << "D::f7() "; }
  virtual void f8() { cout << "D::f8() "; }
  //void f9() override;  // Error, override a nonvirtual function 
};

int main() {
  B b; D d;
  B *bp = &b, *bd = &d; D *dp = &d;
  bp->f1(); bp->f2(); bp->f3(); bp->f6(); bp->f7(); bp->f8(); bp->f9(); cout << endl;
  bd->f1(); bd->f2(); bd->f3(); bd->f6(); bd->f7(); bd->f8(); bd->f9(); cout << endl;
  dp->f1(); dp->f2(); dp->f3(); dp->f6(); dp->f7(); dp->f8(); dp->f9(); cout << endl;
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出是

B::f1() B::f2() B::f3() B::f6() B::f7() B::f8() B::f9()
D::f1() D::f2() D::f3() B::f6() B::f7() B::f8() B::f9()
D::f1() D::f2() D::f3() B::f6() D::f7() D::f8() B::f9()
Run Code Online (Sandbox Code Playgroud)
  1. 比较f1()f6()。我们知道这一点,override并且final在语义上是独立的。

    • override表示该函数正在其基类中覆盖虚拟函数。请参阅f1()f3()
    • final表示该函数不能被其派生类覆盖。(但是函数本身不必重写基类虚函数。)请参见f6()f4()
  2. 比较f2()f3()。我们知道,如果声明一个成员函数不virtual带有和final,则表示它已经覆盖了基类中的虚函数。在这种情况下,关键字override是多余的。

  3. 比较f4()f5()。我们知道,如果用声明成员函数,virtual并且它不是继承层次结构中的第一个虚函数,那么我们应该使用它override来指定重写关系。否则,我们可能会意外地在派生类中添加新的虚函数。

  4. 比较f1()f7()。我们知道,可以在派生类中重写任何成员函数,而不仅仅是虚函数。什么virtual规定是多态的,这意味着决定哪个函数运行被延迟,直到运行时间而不是编译时间。(在实践中应避免这样做。)

  5. 比较f7()f8()。我们知道,我们甚至可以重写基类函数并将其设为新的虚函数。(这意味着f8()派生自该类的任何成员函数D都是虚函数。)(在实践中也应避免这样做。)

  6. 比较f7()f9()。我们知道,override当我们想覆盖派生类中的虚函数而忘记virtual在基类中添加关键字时,可以帮助我们找到错误。

总之,我个人认为最佳实践是:

  • 用于virtual声明基类中的第一个虚函数;
  • override除非final另外指定,否则始终用于在派生类中指定重写虚拟函数。