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

在 C + + 11中,函数使用 final关键字的目的是什么?我知道它可以防止派生类重写函数,但是如果是这样的话,那么将 final函数声明为非虚函数还不够吗?还有什么我不知道的吗?

132559 次浏览
  • 这是为了防止类被继承。来自 维基百科:

    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
    };
    

维基百科进一步推出 一个有趣的观点:

请注意,overridefinal都不是语言关键字。它们是技术上的标识符; 它们只有在特定的语境中才有特殊的意义.在任何其他位置,它们都可以是有效的标识符。

这意味着,以下内容是允许的:

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

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 函数,它就不能在任何派生类中再次声明。

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 这个词扩展到非虚拟成员。不过,它们的含义会有所不同。)

正如 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!
};

Final 关键字允许您声明一个虚方法,重写它 N 次,然后命令“ this can not more be override”。它对于限制派生类的使用非常有用,这样您就可以说: “我知道我的超类允许您重写它,但是如果您想从我这里派生,您就不能!”.

struct Foo
{
virtual void DoStuff();
}


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


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

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

Final 关键字的用途之一是防止意外重写方法。在我的示例中,DoStuff ()可能是一个助手函数,派生类只需要重命名就可以获得正确的行为。没有 final,直到测试才能发现错误。

“ 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()的派生类内部调用。

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

“ 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

关键字添加到函数中时,防止它被派生类重写。 此外,当添加到类中时,还可以防止任何类型的继承。 请考虑下面的示例,该示例显示了最终说明符的使用。

#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;
}

对马里奥•克内佐维(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();

上面的代码展示了该理论,但并没有在真正的编译器上进行实际测试。如果有人粘贴一个反组装输出非常感谢。

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关键字之前。