最终的虚函数有什么意义?

新手上路,请多包涵

Wikipedia 在 C++11 final 修饰符上有以下示例:

 struct Base2 {
    virtual void f() final;
};

struct Derived2 : Base2 {
    void f(); // ill-formed because the virtual function Base2::f has been marked final
};

我不明白引入虚函数并立即将其标记为 final 的意义。这只是一个不好的例子,还是有更多的例子?

原文由 fredoverflow 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 410
2 个回答

通常 final 不会用于基类的虚函数定义。 final 将由重写函数的派生类使用,以防止进一步的派生类型进一步重写函数。由于覆盖函数通常必须是虚拟的,这意味着任何人都可以在进一步的派生类型中覆盖该函数。 final 允许指定一个覆盖另一个但自身不能被覆盖的函数。

例如,如果您正在设计一个类层次结构并需要覆盖一个函数,但您不希望类层次结构的用户也这样做,那么您可以在派生类中将函数标记为 final。


由于它在我想添加的评论中被提到了两次:

有些人让基类将非覆盖方法声明为最终方法的一个原因很简单,因为任何试图在派生类中定义该方法的人都会出错,而不是默默地创建一个“隐藏”基类方法的方法。

 struct Base {
   void test() { std::cout << "Base::test()\n"; }
};

void run(Base *o) {
    o->test();
}

// Some other developer derives a class
struct Derived : Base {
   void test() { std::cout << "Derived::test()\n"; }
};

int main() {
    Derived o;
    o.test();
    run(&o);
}

Base 的开发人员不希望 Derived 的开发人员这样做,并希望它产生错误。所以他们写道:

 struct Base {
    virtual void test() final { ... }
};

使用 Base::foo() 的声明会导致 Derived 的定义产生如下错误:

 <source>:14:13: error: declaration of 'test' overrides a 'final' function
       void test() { std::cout << "Derived::test()\n"; }
            ^
<source>:4:22: note: overridden virtual function is here
        virtual void test() final { std::cout << "Base::test()\n"; }
                     ^

你可以自己决定这个目的是否值得,但我想指出,声明函数 virtual final 并不是防止这种隐藏的完整解决方案。派生类仍然可以隐藏 Base::test() 而不会引发所需的编译器错误:

 struct Derived : Base {
   void test(int = 0) { std::cout << "Derived::test()\n"; }
};

Whether Base::test() is virtual final or not, this definition of Derived is valid and the code Derived o; o.test(); run(&o); behaves exactly the same.

至于对用户的明确声明,我个人认为只是不标记方法 virtual 向用户更清楚地声明该方法不打算被覆盖而不是标记它 virtual final 。但我认为哪种方式更清晰取决于开发人员阅读代码以及他们熟悉的约定。

原文由 bames53 发布,翻译遵循 CC BY-SA 4.0 许可协议

对于要标记的函数 final 它必须是 virtual ,即在 C++11 §10.3 段中。 2:

[…] 为方便起见,我们说任何虚函数都会覆盖自身。

第 4 段:

如果某个类 B 中的虚函数 f 用 virt-specifier final 标记,并且在从 B 派生的类 D 中,函数 D::f 覆盖了 B::f,则程序是非良构的。 […]

即, final 只需要与虚函数(或与阻止继承的类)一起使用。因此,该示例需要使用 virtual 才能使其成为有效的 C++ 代码。

编辑: 要完全清楚:“点”询问了为什么甚至使用虚拟的问题。使用它的底线原因是(i)因为代码不会编译,以及(ii)为什么在一个足够的情况下使用更多类使示例更复杂?因此,仅使用一个具有虚拟最终函数的类作为示例。

原文由 Paul Preney 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题