当前位置: 首页 > 知识库问答 >
问题:

final是否意味着覆盖?

孟栋
2023-03-14

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

我对< code>final关键字的理解是,它告诉编译器任何类都不能覆盖这个< code >虚拟函数。

那么覆盖最终是多余的吗?它似乎编译得很好。覆盖最终传达哪些信息而最终信息不传达?这种组合的用例是什么?

共有3个答案

贺华容
2023-03-14

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

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()。我们知道覆盖最终在语义上是独立的。

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

    比较f2()f3()。我们知道,如果一个成员函数声明时没有<code>virtual</code>,而声明时带有<code>final</code<,这意味着它已经覆盖了基类中的一个虚拟函数。在这种情况下,关键字<code>override</code>是冗余的。

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

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

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

    比较f7()f3()。我们知道,当我们想重写派生类中的虚函数而忘记在基类中添加关键字<code>virtual</code>时,<code>override</code>可以帮助我们找到错误。

    总之,在我看来,最佳做法是:

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

  • 刘令
    2023-03-14

    final不一定意味着函数被重写。在继承层次结构中的第一个声明中将虚拟函数声明为<code>final</code>是完全有效的(如果值有点可疑)。

    我能想到创建一个虚拟的和立即最终的函数的一个原因是,如果你想防止派生类给出相同的名称

    田兴怀
    2023-03-14

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

    如果某个类 B 中的虚函数 f 标有 virt-specifier final,而在派生自 B 的类 D 中,函数 D::f 覆盖了 B :f,则程序格式不正确。

    就是这样。现在 override final 仅意味着
    “此函数覆盖基类 1(覆盖),并且不能重写自身(final)”。
    最终本身将施加较弱的要求。覆盖最终具有独立的行为。

    注意< code>final只能用于虚拟函数,尽管- [class.mem]/8

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

    因此,声明

    void foo() final;
    

    实际上与

    virtual void foo() final override;
    

    由于两者都需要<code>foo</code>来重写某些东西,所以第二个声明通过使用<code>override</code>来实现,第一个声明通过当且仅当<code>foo>是隐式虚拟的时才有效,即当<code>foo</code>正在重写基类中名为<code>foo</code>的虚拟函数时,这使得派生类中的<code>foo</cocode>自动虚拟。因此,在出现<code>final</code>而不是<code>virtual</code<的声明中,<code>override</code>将是多余的
    尽管如此,后一种声明表达的意图更加明确,应该是首选。

     类似资料:
    • 链接的问题不一样 - 甚至没有提到 编辑:新的副本列表包含一个合法的副本,我在搜索中没有找到。 在问这个问题之前,我不知道是否在派生类成员中使用的选择对某些人来说将是一个有争议的问题。 我刚刚遇到了一些如下所示的源代码: 我不确定如何解释这个,甚至在读完这个之后。 这里的< code>override是指< code>virtual吗?< code>void B::method()未被标记为虚函数

    • 我知道CloudBlockBlob。DeleteIfExists()如果blob存在,则返回true;如果不存在,则返回false。 然而,我很好奇,如果blob确实存在,会发生什么情况,但Azure中出现了一些错误,导致文件删除没有发生(我找不到任何关于该行为的文档)。 我担心它会返回false而不是抛出某种异常,所以我相信blob会被删除,而它实际上仍然存在。 简而言之,如果我得到一个fals

    • 我想创建一个

    • 当服务器响应附带“content-encoding:gzip”时,是否一定意味着响应主体是gzip格式的?我正在解码传入的响应正文(可能不正确),并得到一个“不在GZIP格式”异常。

    • 问题内容: 当我们创建扩展抽象类的Subclass对象时,抽象类构造函数也会运行。但是我们知道我们不能创建抽象类的对象。因此,这是否意味着即使构造函数无任何例外地完成运行,也不能保证是否创建了对象吗? 问题答案: 因此,这是否意味着即使构造函数无任何例外地完成运行,也不能保证是否创建了对象吗? 简而言之,a 不会创建对象。它只是 初始化 对象的 状态 。创建对象的是操作员。现在,让我们详细了解一下

    • 你能详细解释一下吗?最后给我举个例子(一个是附加的,一个是分离的),说明iText分离签名的确切含义? 我发现了这个精彩的留档: iText数字签名pdf关于iText数字签名,但我仍然不确定我是否理解iText分离签名的概念。 阅读文档(参见链接),我发现了以下定义: 在PDF中,我们有时指的是分离的签名。根据维基百科的说法,分离签名是一种“与签名数据分离”的数字签名,而不是“捆绑在一起形成一个