【问题标题】:What is the purpose of the "final" keyword in C++11 for functions?C ++ 11中函数的“final”关键字的目的是什么?
【发布时间】:2012-02-08 02:56:42
【问题描述】:

C++11 中final 关键字用于函数的目的是什么?我知道它可以防止派生类覆盖函数,但如果是这种情况,那么将 final 函数声明为非虚拟还不够吗?我还有什么遗漏的吗?

【问题讨论】:

  • "将您的“最终”函数声明为非虚拟还不够" 不,无论您是否使用 virtual 关键字,覆盖函数都是隐式虚拟的.
  • @ildjarn 如果它们没有在超类中被声明为虚拟,那是不正确的,你不能从一个类派生并将非虚拟方法转换为虚拟方法..
  • @DanO 我认为你不能覆盖,但你可以用这种方式“隐藏”一个方法..这会导致很多问题,因为人们并不打算隐藏方法。
  • @DanO :如果它在超类中不是虚拟的,那么它就不会被“覆盖”。
  • 再次,“overriding”在这里有一个特定的含义,就是赋予虚函数多态行为。在您的示例中,func 不是虚拟的,因此无需覆盖任何内容,因此无需将其标记为 overridefinal

标签: c++ c++11 final


【解决方案1】:

正如 idljarn 在评论中已经提到的那样,您缺少的是,如果您从基类中覆盖一个函数,那么您不可能将其标记为非虚拟:

struct base {
   virtual void f();
};
struct derived : base {
   void f() final;       // virtual as it overrides base::f
};
struct mostderived : derived {
   //void f();           // error: cannot override!
};

【讨论】:

  • 谢谢!这是我遗漏的一点:即即使您的“叶子”类也需要将其功能标记为虚拟,即使它们打算覆盖函数,而不是自己被覆盖
  • @lezebulon:如果超类将函数声明为虚拟,则叶类不需要将函数标记为虚拟。
  • 如果叶类中的方法在基类中是虚拟的,则它们是隐式虚拟的。我认为如果缺少这个隐含的“虚拟”,编译器应该发出警告。
  • @AaronMcDaid:编译器通常会警告正确的代码可能会导致混淆或错误。我从来没有见过任何人对这种语言的这个特殊特性感到惊讶,这种方式可能会导致任何问题,所以我真的不知道这个错误有多大用处。相反,忘记virtual 可能会导致错误,C++11 将override 标记添加到一个函数中,该标记将检测到这种情况,并且当一个旨在覆盖的函数时编译失败> 实际上隐藏
  • 从 GCC 4.9 更改说明:“改进去虚拟化的新类型继承分析模块。去虚拟化现在考虑匿名命名空间和 C++11 最终关键字” - 所以它不仅仅是语法糖,它还具有潜在的优化优势。
【解决方案2】:
  • 是为了防止类被继承。来自Wikipedia

    C++11 还增加了防止从类继承或简单地防止在派生类中覆盖方法的能力。这是通过特殊标识符 final 完成的。例如:

    struct Base1 final { };
    
    struct Derived1 : Base1 { }; // ill-formed because the class Base1 
                                 // has been marked final
    
  • 它也用于标记一个虚函数,以防止它在派生类中被覆盖:

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

维基百科进一步使an interesting point

请注意,overridefinal 都不是语言关键字。它们在技术上是标识符; 它们只有在那些特定的上下文中使用时才会获得特殊的含义在任何其他位置,它们都可以是有效的标识符。

也就是说,以下是允许的:

int const final = 0;     // ok
int const override = 1;  // ok

【讨论】:

  • 谢谢,但我忘了提到我的问题是关于“final”与方法的使用
  • 你确实提到了@lezebulon :-)“C++11 中“final”关键字的目的是什么函数”。 (我的重点)
  • 您编辑了它?我没有看到任何显示“由 lezebulon 于 x 分钟前编辑”的消息。那是怎么发生的?也许你提交后很快就编辑了?
  • @Aaron :发布后五分钟内所做的编辑不会反映在修订历史记录中。
  • @Nawaz:为什么它们不是关键字只是说明符?是否由于兼容性原因意味着 C++11 之前的预先存在的代码可能将 final & override 用于其他目的?
【解决方案3】:

“final”还允许编译器优化绕过间接调用:

class IAbstract
{
public:
  virtual void DoSomething() = 0;
};

class CDerived : public IAbstract
{
  void DoSomething() final { m_x = 1 ; }

  void Blah( void ) { DoSomething(); }

};

使用“final”,编译器可以直接从Blah() 内部调用CDerived::DoSomething(),甚至可以内联。没有它,它必须在Blah() 内部生成间接调用,因为Blah() 可以在已覆盖DoSomething() 的派生类内部调用。

【讨论】:

  • 这是这里最有用的答案。 IMO,高分答案是正确的,但和 cppreference 一样无聊。
【解决方案4】:

没有什么可以添加到“final”的语义方面。

但我想在 chris green 的评论中补充一点,“final”在不久的将来可能会成为一种非常重要的编译器优化技术。不仅在他提到的简单案例中,而且对于更复杂的现实世界类层次结构,这些层次结构可以被“final”“关闭”,从而允许编译器生成比通常的 vtable 方法更有效的调度代码。

vtables 的一个主要缺点是,对于任何这样的虚拟对象(假设在典型的 Intel CPU 上是 64 位),指针单独占用了 25%(64 字节中的 8 字节)的高速缓存行。在我喜欢编写的那种应用程序中,这非常痛苦。 (根据我的经验,从纯粹的性能角度来看,它是反对 C++ 的第一论点,即 C 程序员。)

在需要极高性能的应用程序中,这对于 C++ 来说并不罕见,这可能确实变得很棒,不需要以 C 风格手动解决这个问题或奇怪的模板杂耍。

这种技术称为去虚拟化。一个值得记住的词。 :-)

Andrei Alexandrescu 最近发表了一篇精彩的演讲,很好地解释了如何解决今天的此类情况,以及“最终”如何成为未来“自动”解决类似案件的一部分(与听众讨论):

http://channel9.msdn.com/Events/GoingNative/2013/Writing-Quick-Code-in-Cpp-Quickly

【讨论】:

  • 现在有人知道使用这些的编译器吗?
  • 和我想说的一样。
  • @VincentFourmond @crazii 我在 SO 上找到了另一个 answer,这证实了 GCC、MSVC 和 Clang 使用 final 进行去虚拟化优化。检查 cmets 以获取添加此功能的特定编译器版本,甚至链接到编译器代码。
  • @htmlboss 谢谢!
【解决方案5】:

Final 不能应用于非虚函数。

error: only virtual member functions can be marked 'final'

能够将非虚拟方法标记为“final”并不是很有意义。给定

struct A { void foo(); };
struct B : public A { void foo(); };
A * a = new B;
a -> foo(); // this will call A :: foo anyway, regardless of whether there is a B::foo

a->foo() 将始终调用A::foo

但是,如果 A::foo 是 virtual,那么 B::foo 会覆盖它。这可能是不可取的,因此将虚函数设为 final 是有意义的。

问题是,为什么允许对虚函数使用 final。如果您的层次结构较深:

struct A            { virtual void foo(); };
struct B : public A { virtual void foo(); };
struct C : public B { virtual void foo() final; };
struct D : public C { /* cannot override foo */ };

然后final 就可以完成多少覆盖设置“下限”。其他类可以扩展 A 和 B 并覆盖它们的foo,但是如果一个类扩展了 C 则不允许。

因此,将'顶级' foo final 设为可能没有意义,但它可能更有意义。

(但我认为,有空间将 final 和 override 扩展到非虚拟成员。不过它们会有不同的含义。)

【讨论】:

  • 感谢您的示例,这是我不确定的事情。但是仍然:拥有最终(和虚拟)功能有什么意义?基本上你永远无法使用函数是虚拟的这一事实,因为它不能被覆盖
  • @lezebulon,我编辑了我的问题。但后来我注意到了 DanO 的回答——这是我想说的一个很好的明确回答。
  • 我不是专家,但我觉得有时做一个顶级函数final 可能是有意义的。例如,如果您知道您希望所有Shapes 到foo() — 一些预定义且明确的东西,派生形状不应修改。或者,我错了,有更好的模式可以用于这种情况?编辑:哦,也许是因为在这种情况下,一开始就不应该成为顶级foo()virtual?但是,即使通过Shape* 正确(多态地)调用它,它仍然可以被隐藏...
  • 为什么不公开 B { void foo() final; }; ?如果我们已经放了 final,为什么还要在 B 类中放 virtual?
【解决方案6】:

我喜欢的“final”关键字的用例如下:

// This pure abstract interface creates a way
// for unit test suites to stub-out Foo objects
class FooInterface
{
public:
   virtual void DoSomething() = 0;
private:
   virtual void DoSomethingImpl() = 0;
};

// Implement Non-Virtual Interface Pattern in FooBase using final
// (Alternatively implement the Template Pattern in FooBase using final)
class FooBase : public FooInterface
{
public:
    virtual void DoSomething() final { DoFirst(); DoSomethingImpl(); DoLast(); }
private:
    virtual void DoSomethingImpl() { /* left for derived classes to customize */ }
    void DoFirst(); // no derived customization allowed here
    void DoLast(); // no derived customization allowed here either
};

// Feel secure knowing that unit test suites can stub you out at the FooInterface level
// if necessary
// Feel doubly secure knowing that your children cannot violate your Template Pattern
// When DoSomething is called from a FooBase * you know without a doubt that
// DoFirst will execute before DoSomethingImpl, and DoLast will execute after.
class FooDerived : public FooBase
{
private:
    virtual void DoSomethingImpl() {/* customize DoSomething at this location */}
};

【讨论】:

  • 是的,这本质上是模板方法模式的一个例子。而在 C++11 之前,正是 TMP 让我希望 C++ 像 Java 那样具有诸如“final”之类的语言特性。
【解决方案7】:

final 添加了一个明确的意图,即不覆盖您的函数,如果违反这一点,将导致编译器错误:

struct A {
    virtual int foo(); // #1
};
struct B : A {
    int foo();
};

就代码而言,它可以编译,并且B::foo 会覆盖A::foo。顺便说一句,B::foo 也是虚拟的。但是,如果我们将#1 更改为virtual int foo() final,那么这是一个编译器错误,我们不允许在派生类中进一步覆盖A::foo

请注意,这不允许我们“重新打开”新的层次结构,即无法使 B::foo 成为一个新的、不相关的函数,它可以独立地位于新的虚拟层次结构的头部。一旦一个函数是 final 的,就不能在任何派生类中再次声明它。

【讨论】:

    【解决方案8】:

    final 关键字允许您声明一个虚拟方法,将其覆盖 N 次,然后强制要求“不能再覆盖它”。它在限制派生类的使用方面很有用,这样你就可以说“我知道我的超类允许你重写它,但是如果你想从我那里派生,你不能!”。

    struct Foo
    {
       virtual void DoStuff();
    }
    
    struct Bar : public Foo
    {
       void DoStuff() final;
    }
    
    struct Babar : public Bar
    {
       void DoStuff(); // error!
    }
    

    正如其他发帖者所指出的,它不能应用于非虚拟功能。

    final 关键字的一个目的是防止意外覆盖方法。在我的示例中,DoStuff() 可能是一个辅助函数,派生类只需重命名它即可获得正确的行为。没有final,直到测试才发现错误。

    【讨论】:

      【解决方案9】:

      将 C++ 中的 Final 关键字添加到函数时,可防止它被派生类覆盖。 此外,当添加到类中时,可以防止任何类型的继承。 考虑以下示例,该示例显示了 final 说明符的使用。该程序编译失败。

      #include <iostream>
      using namespace std;
      
      class Base
      {
        public:
        virtual void myfun() final
        {
          cout << "myfun() in Base";
        }
      };
      class Derived : public Base
      {
        void myfun()
        {
          cout << "myfun() in Derived\n";
        }
      };
      
      int main()
      {
        Derived d;
        Base &b = d;
        b.myfun();
        return 0;
      }
      

      还有:

      #include <iostream>
      class Base final
      {
      };
      
      class Derived : public Base
      {
      };
      
      int main()
      {
        Derived d;
        return 0;
      }
      

      【讨论】:

      • 基类中的 virtual void myfun() final 显然没有意义。
      【解决方案10】:

      final关键字在C++中有以下用途

      1. 如果将基类中的virtual 方法设为final,则不能在派生类中覆盖它。它会显示一个编译错误:
      class Base {
      public:
          virtual void display() final  {
              cout << "from base" << endl;
          }
      };
      class Child : public Base {
      public:
          void display() {
              cout << "from child" << endl;
          }
      };
      int main() {
          Base *b = new Child();
          b->display();
          cin.get();
          return 0;
      }
      
      
      1. 如果我们将一个类设为final,它的子类就不能继承它:
      class Base final {
      public:
          void displayBase()   {
              cout << "from base" << endl;
          }
      };
      class Child :public Base {
      public:
          void displayChild() {
              cout << "from child" << endl;
          }
      };
      
      

      注意:在Java中与final关键字的主要区别是, a) final 实际上不是 C++ 中的关键字。 你可以在 C++ 中有一个名为 final 的变量 b) 在 Java 中,final 关键字总是添加在 class 关键字之前。

      【讨论】:

        【解决方案11】:

        补充 Mario Knezović 的回答:

        class IA
        {
        public:
          virtual int getNum() const = 0;
        };
        
        class BaseA : public IA
        {
        public:
         inline virtual int getNum() const final {return ...};
        };
        
        class ImplA : public BaseA {...};
        
        IA* pa = ...;
        ...
        ImplA* impla = static_cast<ImplA*>(pa);
        
        //the following line should cause compiler to use the inlined function BaseA::getNum(), 
        //instead of dynamic binding (via vtable or something).
        //any class/subclass of BaseA will benefit from it
        
        int n = impla->getNum();
        

        以上代码展示了理论,但并未在真实编译器上实际测试过。如果有人粘贴反汇编的输出,将不胜感激。

        【讨论】:

          猜你喜欢
          • 2014-05-11
          • 1970-01-01
          • 2014-12-05
          • 2011-02-08
          • 2013-06-22
          • 2011-12-10
          • 1970-01-01
          • 1970-01-01
          • 2014-08-15
          相关资源
          最近更新 更多