C++中的final关键字是否意味着在所有方面都是最终的?

23

C++11添加了final。

终于!

我理解final有两个作用:

  • 使类不可被继承。
  • 使类中的(虚)函数在派生类中不可重写。

这两者似乎相互独立。但以以下示例为例:

class Foo
{
    public:
    virtual void bar()
    {
        //do something unimportant.
    }
};
class Baz final : public Foo
{
    public:
    void bar() /*final*/ override
    {
        //do something more important than Foo's bar.
    }
};

从上面的内容可以看出,我认为Bazfinal的,因此我不需要指定它的virtual成员函数bar也是final的。由于Baz无法被继承,覆盖bar的问题已经超出了范围。然而,我的编译器VC++ 2015在这方面非常安静。目前我还没有在其他编译器上进行测试。

如果有人能够解决这个问题,我会很高兴。如果有标准(如果有的话)的引用,那就更好了。同时,请说明我不知道的任何边界情况,这可能会导致我的逻辑信念失败。

所以,我的问题是:一个final class是否会隐含地使其virtual函数也变成final?应该吗?请澄清。


我提出这个问题的原因是final函数有资格进行去虚拟化,这是一个很好的优化。感谢您的帮助。


2
只有当静态类型与函数所在的类型匹配时,最终函数才会被去虚拟化。这种情况很少发生,并且通常表明首先根本不需要虚拟。 - SergeyA
2
如果编译器可以保证不需要虚函数调用,那么它将不会插入虚函数调用(作为一种优化)。编译器很聪明,在引入 final 之前就已经这样做了。但我想现在更容易了。 - Martin York
2
如果你将你的类标记为final,那么你就不能从它继承,因此你也无法覆盖它的任何成员函数,那么标记成员函数是否为final还有什么意义呢? - MikeMB
2
编译器可以将final类的所有成员函数视为已声明为final - M.M
@LightnessRacesinOrbit 是的。这就是为什么我问是否在这种情况下可以暗示使用 final 修饰符(应用于成员虚函数)。 - Anirban Sarkar
显示剩余9条评论
3个回答

20
我之所以问这个问题,是因为最终函数有资格进行去虚拟化,这是一个很好的优化。
真的吗?“去虚拟化”不是C++标准的一部分。或者说,不完全是。
去虚拟化只是“as if(仿佛)”规则的结果,该规则规定实现可以做任何它想做的事情,只要实现表现得好像它正在做标准所说的事情。
如果编译器能在编译时检测到通过多态类型调用特定版本的虚成员函数将无可辩驳地调用该函数,则允许避免使用虚分派逻辑并静态调用该函数。这就好像使用了虚分派逻辑,因为编译器可以证明这是将要调用的函数。
因此,标准没有定义何时允许/禁止去虚拟化。编译器在内联接受指向基类类型的指针的函数时,可能会发现被传递的指针指向在其内联中声明的堆栈变量。或者编译器可以跟踪特定的内联/调用图,追溯到特定多态指针/引用的起点。在这些情况下,编译器可以对该类型进行去虚拟化调用。但只有当编译器足够聪明时才能这样做。
一个编译器是否会对所有最终类的虚函数调用进行去虚拟化,而不管这些方法是否已经声明为“final”?它可能会。也可能不会。甚至可能不会去虚拟化任何在多态类型上声明为“final”的方法的调用。这种实现是有效的(尽管不是特别明智)。
您所提出的问题是与实现相关的。它可能因编译器而异。

然而,正如您所指出的,一个被声明为final的类应该足以告知编译器将所有对指向或引用final类类型的指针进行去虚化。如果编译器没有这样做,那么这是一个实现质量问题,而不是标准问题。


不行。整个虚拟化机制(通过虚拟表和指针实现)并不是标准的一部分。我本来希望实现者能够发布有关所实现的具体细节的信息。 无论如何,对于明确的回答加一。 - Anirban Sarkar

7

引用C++标准草案中的文字 [class.virtual/4]

如果某个类B中的虚函数f被标记为final,并且在派生自B的类D中,D::f覆盖了B::f,则程序是非法的。

以及这里 [class/3]

如果一个类被标记为final,并且作为基类出现在基类从句(第[class.derived]条)中,则程序是非法的。

因此,回答问题:

一个final类是否会隐式地将其virtual函数也声明为final?它应该这样做吗?请澄清。

至少在形式上不是这样的。任何违反规则的尝试都会导致相同的结果;程序无法正常运行,无法编译。一个final class意味着该类不能被继承,因此,其virtual方法不能被覆盖。

它应该这样做吗?至少在形式上,可能不需要;它们是相关的,但它们并不是同一件事。也没有必要正式要求其中之一暗示另一个,效果自然而然地产生。任何违规行为都会导致相同的结果,编译失败(希望有适当的错误消息来区分两者)。


谈及您提出的问题和虚拟调用的去虚拟化动机。这并不总是立即受到类或方法的final影响(尽管它们提供帮助),普通的虚函数和类层次结构规则适用。

如果编译器可以确定在运行时将始终调用特定方法(例如使用自动对象,即“在堆栈上”),则无论该方法是否被标记为final,它都可以应用这种优化。这些优化遵循“as-if”规则,允许编译器应用任何转换,只要可观察的行为与原始代码执行一样。


哎?它怎么会是“格式不正确”的呢?“final class”应该意味着将其自己的虚继承成员函数作为“final”。上面的示例在两个测试用例中都编译通过:当将“final”说明符附加到虚函数时,以及不附加时(没有任何错误/警告/信息消息)。请解释一下。 - Anirban Sarkar
是的,标准措辞(如所要求)涉及当试图覆盖最终方法或将最终类作为基础时。这就是final的目的。final关键字可以组合使用,这没有问题,两种情况意义不同。如果您担心代码的未来维护,甚至可以建议这样做。您提到的优化是实现质量问题,并且在final添加到标准之前就已存在。 - Niall

3

一个 final 类隐式地将其 virtual 函数也变成了 final 吗?

[...]

我提出这个问题是因为,对于 final 函数来说,它们可以获得优化的 de-virtualization 认证。

是的,所有主流编译器(包括 MSVC)都会为了实现 de-virtualization 优化而将其视为 final

struct B                   { virtual void f() = 0;   };

struct D1       : public B {         void f();       };
struct D2       : public B {         void f() final; };
struct D3 final : public B {         void f();       };

void f1(D1& x) { x.f(); } // Not de-virtualized
void f2(D2& x) { x.f(); } //     De-virtualized
void f3(D3& x) { x.f(); } //     De-virtualized

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接