C ++ 11中“final”关键字用于函数的目的是什么?

时间:2012-01-11 18:25:34

标签: c++ c++11 final

C ++ 11中final关键字用于函数的目的是什么?我理解它可以防止派生类的函数覆盖,但如果是这种情况,那么将final函数声明为非虚拟是不够的?还有另外一件我在这里失踪的事吗?

11 个答案:

答案 0 :(得分:113)

  • 这是为了防止类被继承。来自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

答案 1 :(得分:112)

你缺少什么,因为评论中已经提到的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!
};

答案 2 :(得分:39)

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

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

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

  void Blah( void ) { DoSomething(); }

};

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

答案 3 :(得分:27)

无法添加到“最终”的语义方面。

但我想补充一下克里斯格林的评论,即“最终”可能会在不久的将来成为一种非常重要的编译器优化技术。不仅在他提到的简单案例中,而且在更复杂的现实世界的类层次结构中,它们可以被“最终”“封闭”,从而允许编译器生成比通常的vtable方法更有效的调度代码。

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

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

此技术称为 Devirtualization 。这个词值得记住。 : - )

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

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

答案 4 :(得分:7)

Final不能应用于非虚函数。

error: only virtual member functions can be marked '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会覆盖它。这可能是不合需要的,因此将虚函数设为最终是有意义的。

问题是,为什么允许最终使用虚拟功能。如果您有深层次结构:

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可能没有意义,但它可能会降低。

(我认为,有空间可以扩展最终的词语并覆盖非虚拟成员。但它们会有不同的含义。)

答案 5 :(得分:7)

我喜欢的'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 */}
};

答案 6 :(得分:4)

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成为一个新的,无关的函数,它可以独立地位于新的虚拟层次结构的头部。一旦函数是最终的,它就永远不会在任何派生类中再次声明。

答案 7 :(得分:4)

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

struct Foo
{
   virtual void DoStuff();
}

struct Bar : public Foo
{
   void DoStuff() final;
}

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

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

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

答案 8 :(得分:2)

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

  1. 如果您在基类中将虚拟方法设为 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关键字总是加在类关键字之前。

答案 9 :(得分:1)

添加到函数中的C ++中的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;
}

答案 10 :(得分:0)

马里奥·克涅佐维奇的答案的补充:

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();

上面的代码显示了理论,但并未在实际的编译器上进行实际测试。非常感谢有人粘贴反汇编的输出。