final 是不是意味着覆盖?

Posted

技术标签:

【中文标题】final 是不是意味着覆盖?【英文标题】:Does final imply override?final 是否意味着覆盖? 【发布时间】:2015-04-02 11:58:45 【问题描述】:

据我了解,override 关键字表明给定声明实现了基本 virtual 方法,如果找不到匹配的基本方法,编译应该会失败。

我对@9​​87654324@ 关键字的理解是它告诉编译器任何类都不能覆盖这个virtual 函数。

那么override final 是多余的吗? It seems to compile fine。 override final 传达了哪些final 没有传达的信息?这种组合的用例是什么?

【问题讨论】:

【参考方案1】:

final 不需要函数首先覆盖任何内容。它的效果在[class.virtual]/4中定义为

如果某个类 B 中的虚函数 f 被标记为 virt-specifier final 和一个类 D 派生自 B 一个函数 D::f 覆盖B::f,程序格式错误。

就是这样。现在override final 只是意味着 „此函数会覆盖一个基类 (override),并且不能被其自身覆盖 (final)。“final 本身会施加较弱的要求。 overridefinal 具有独立的行为。


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

virt-specifier-seq 应该只出现在 a 的声明中 虚拟成员函数 (10.3)。

因此声明

void foo() final;

实际上与

相同
virtual void foo() final override;

由于两者都需要foo 覆盖某些东西 - 第二个声明使用override,第一个声明当且仅当foo 是隐式虚拟时才有效,即foo 被覆盖时基类中称为foo 的虚函数,它使派生类中的foo 自动为虚函数。 因此,override 在出现final 而不是virtual 的声明中是多余的。 不过,后一种声明更清楚地表达了意图,绝对应该首选。

【讨论】:

我喜欢你的回答,但我想澄清一下,从实际的角度来看,virtual void f() final overridevoid f() final 是等效的,因为如果它们不覆盖某些东西,它们都会失败. final 仅对虚函数有效,f 的后一个声明仅在覆盖函数时才为虚函数。不过,后者的错误消息可能不太准确。 后一个声明更清楚地表达了意图 - 我不这么认为。您的描述使我更喜欢第一种声明样式,原因与我在覆盖虚拟方法时避免添加virtual 的原因相同:它没有增加任何价值。不过当然比写virtual void foo() final; @Wolf 你不认为它表达的意图更清楚吗?您能否准确地阐明第一份声明表所表达的意图? 我认为final 必须在你的描述之后是virtual(我只是在学习这些新功能)并且由于你省略了virtual关键字,它如果不是override,则不会编译。或者,也许我忽略了一个可能导致不良行为的案例?使用非覆盖最终虚拟我只是想弄清楚下面Angew's answer ... 我应该补充一点,您在答案中列出的机器人变体的意图很明确,只有 virtual void foo() final; 不清楚。【参考方案2】:

final 并不一定意味着该函数被覆盖。在继承层次结构中的 first 声明中将虚函数声明为 final 是完全有效的(如果其价值有些可疑)。

我能想到创建一个虚拟且立即终止的函数的一个原因是,如果您想防止派生类赋予相同的名称和参数不同的含义。

【讨论】:

+1 用于展示虚拟/立即终止函数的可行用例。我想知道编译器在完成后是否仍会生成一个 vtable? @Carlton 这取决于编译器。然而,就标准而言,它使类具有多态性 - 例如,这意味着dynamic_cast 必须为它工作。我相信虚函数的 vtable 实现也需要 vtable 才能使dynamic_cast 工作。 呸,我刚刚根据我之前的评论发布了一个问题,但你几乎只是在这里回答了它。谢谢。 如果要防止派生类赋予相同的名称和参数不同的含义,请不要将其设为虚方法。我在这个模糊的描述中看不到可行的用例(不像@Carlton),但我真的对一个很感兴趣。 @Wolf 当然,我的意思是相同的非限定名称。注释不能很好地显示代码,但我写了一个example offsite。 Derived2 无法隐藏mustRemainBase【参考方案3】:

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

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;

输出是

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()

    比较 f1()f6()。我们知道overridefinal 在语义上是独立的。

    override 表示该函数正在覆盖其基类中的虚函数。请参阅f1()f3()final 表示该函数不能被其派生类覆盖。 (但函数本身不需要覆盖基类虚函数。)参见f6()f4()

    比较 f2()f3()。我们知道,如果一个成员函数被声明为没有virtualfinal,这意味着它已经覆盖了基类中的一个虚函数。在这种情况下,关键字override 是多余的。

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

    比较 f1()f7()。我们知道任何成员函数,而不仅仅是虚拟函数,都可以在派生类中被覆盖。 virtual 指定的是多态性,这意味着关于运行哪个函数的决定被延迟到运行时而不是编译时。 (在实践中应该避免这种情况。)

    比较 f7()f8()。我们知道我们甚至可以覆盖一个基类函数并使其成为一个新的虚拟函数。 (这意味着从D 派生的类的任何成员函数f8() 都是虚拟的。)(这在实践中也应该避免。)

    比较 f7()f9()。我们知道override可以帮助我们在派生类中重写虚函数而忘记在基类中添加关键字virtual时发现错误。

总结,我个人认为的最佳做法是:

在基类中第一个虚函数的声明中使用virtual; 始终使用override 指定派生类中的重写虚函数,除非还指定了final

【讨论】:

【参考方案4】:

以下代码(带有final 说明符)编译。但是当final 被替换为override final 时编译会失败。因此override finalfinal 传达更多信息(并阻止编译)。

class Base

public:
    virtual ~Base() 
;

class Derived : public Base

public:
    virtual void foo() final
    
        std::cout << "in Derived foo\n";
    
;

基本上,override final 表示此方法不能在任何派生类中被覆盖并且此方法会覆盖基类中的虚拟方法。 final 单独没有指定基类覆盖部分。

【讨论】:

"final 本身并没有指定基类覆盖部分。"正确,但void foo() final 可以。【参考方案5】:

没有final 不一定暗示override。实际上,您可以声明一个 virtual 函数,然后立即声明 final see here。 final 关键字只是声明没有派生的class 可以创建此函数的覆盖。

override 关键字很重要,因为它强制您确实在覆盖一个虚函数(而不是声明一个新的不相关的函数)。见this post regarding override

长话短说,它们各自都有自己的特定目的,通常两者都使用是正确的。

【讨论】:

嗯,我希望你发布的第一个示例不会编译,因为我看到它声明一个虚函数并同时使其最终化的方式完全没有意义。 cpp 核心指南说只使用一个github.com/isocpp/CppCoreGuidelines/blob/master/…

以上是关于final 是不是意味着覆盖?的主要内容,如果未能解决你的问题,请参考以下文章

协议方法是不是意味着在 Swift 中被覆盖?

final, finally, finalize的区别描述

如何让 Wordpress 页面覆盖现有目录(不是子目录)?

如何监控 android.util.log 或覆盖它?

static & abstract

Java中的覆盖和隐藏以及final关键字