【问题标题】:Does C++ final imply final in all aspects?C++ final 是否在所有方面都暗示 final?
【发布时间】:2016-08-22 01:22:49
【问题描述】:

C++11 添加了最终版本。

终于!

我知道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也是@987654327 @。由于Baz 不能被继承,覆盖bar 的问题超出了范围。但是我的编译器 VC++ 2015 对此非常安静。目前我还没有在其他任何人身上测试过。

如果有人能对这个话题有所了解,我会很高兴。来自标准的报价(如果有的话)将不胜感激。还请说明我不知道的任何极端情况,这可能会导致我的逻辑信念失败。

所以,我的问题是:是否有一个 final class 隐含 virtual 功能是 final 也?应该是?请澄清。


我问这个的原因是因为final 函数符合去虚拟化的条件,这是一个很好的优化。任何帮助表示赞赏。

【问题讨论】:

  • final 函数仅在静态类型与函数为 final 的类型匹配时才被去虚拟化。很少发生,通常表明一开始就不需要虚拟。
  • @SergeyA 是的。这就是我写合格的原因。
  • 如果编译器可以保证它不需要虚拟调用,那么它就不会植入虚拟调用(作为优化)。编译器很聪明,在引入 final 之前已经这样做了。但我想现在更容易了。
  • 如果你将你的类标记为final,你就不能从它继承,因此你不能覆盖它的任何成员函数,那么为什么还要麻烦一个成员函数是否是final呢?
  • 允许编译器将final类的所有成员函数视为声明final

标签: c++ c++11 virtual final


【解决方案1】:

从这里引用 C++ 标准草案[class.virtual/4]

如果某个类B 中的一个虚函数fvirt-specifier 标记final 并且在一个从B 派生的类D 中是一个函数D::f覆盖B::f,程序格式错误。

这里是[class/3]

如果一个类被标记为 class-virt-specifier final 并且它在 base-clause 中显示为 base-type-specifier (子句 [class.derived]),程序格式错误。

所以,回答这个问题;

finalclass 是否隐含地暗示其virtual 函数也为final?应该是?请澄清。

所以,至少不是正式的。任何违反任一规则的企图在两种情况下都会产生相同的结果;该程序格式错误,无法编译。 final class 表示类不能派生,因此,它的 virtual 方法不能被覆盖。

应该吗?至少在形式上,可能不是;它们是相关的,但它们不是一回事。也无需正式要求一者暗示另一者,效果自然而然。任何违规都会产生相同的结果,即编译失败(希望有适当的错误消息来区分两者)。


谈谈您的查询动机和虚拟呼叫的去虚拟化。这并不总是立即受到类或方法的final 的影响(尽管它们提供了帮助),虚函数和类层次结构的正常规则适用。

如果编译器可以确定在运行时将始终调用特定方法(例如,使用自动对象,即“在堆栈上”),它可以应用这样的an optimisation anyway,而不管方法是否标记为 final .这些优化属于“as-if”规则,只要可观察到的行为就像原始代码已执行,就允许编译器 apply any transformation

【讨论】:

  • 呃?它是如何格式错误的final class 应该暗示 它自己的 虚拟继承的成员函数为 final。上面的示例在两个测试用例中都编译:当 final 说明符附加到虚拟函数时,以及何时没有(没有任何错误/警告/信息性消息)。请解释一下。
  • 是的,您(按要求)的标准措辞与尝试覆盖最终方法或将最终类作为基础的尝试有关。这就是final的目的。 final 关键字可以组合,没有问题,这两种情况不是一个意思。如果您对代码的未来维护有疑虑,甚至可以建议您。您提到的优化是实施质量问题,并且在 final 被添加到标准之前很久就已经存在。
【解决方案2】:

final 类是否隐含地暗示其virtual 函数也是final

[...]

我问这是因为最终函数符合去虚拟化的条件,这是一个很好的优化。

是的,出于去虚拟化的目的,在所有主要编译器(包括 MSVC)中都会这样做:

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

【讨论】:

    【解决方案3】:

    我问这个的原因是因为最终函数可以进行去虚拟化,这是一个很好的优化。

    有吗? “去虚拟化”不是 C++ 标准的一部分。或者至少,不是。

    去虚拟化只是“好像”规则的结果,该规则规定实现可以做任何它喜欢的事情,只要实现表现得“好像”它正在按照标准所说的那样行事。

    如果编译器可以在编译时检测到通过多态类型对虚拟成员函数的特定调用将无可否认地调用该函数的特定版本,则允许避免使用虚拟调度逻辑和调用静态函数。这表现得“好像”它使用了虚拟调度逻辑,因为编译器可以证明这是本应调用的函数。

    因此,该标准没有定义何时允许/禁止去虚拟化。编译器在内联一个带有指向基类类型的指针的函数时,可能会发现被传递的指针指向一个堆栈变量,该变量是在它被内联的函数中声明的。或者编译器可以将特定的内联/调用图追踪到特定多态指针/引用的起点。在这些情况下,编译器可以将调用去虚拟化为该类型。但前提是它足够聪明。

    编译器是否会将所有对final 类的虚函数调用去虚拟化,而不管这些方法本身是否声明为final?有可能。它可能不会。它甚至可能不会对在多态类型上声明为 final 的方法的任何调用进行去虚拟化。这是一个有效的(如果不是特别聪明的话)实现。

    您要问的问题是特定于实现的。它可能因编译器而异。

    但是,正如您所指出的,声明为 final 的类应该足以让编译器将所有对 final 类类型的指针/引用的调用去虚拟化。如果编译器不这样做,那就是实现质量问题,而不是标准问题。

    【讨论】:

    • 不。整个虚拟化机制(使用虚拟表和指针完成)不是标准的一部分。我希望实施者会发布有关他们实施的实施细节的详细信息。无论如何,+1 以获得明确的答案。
    猜你喜欢
    • 2020-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多