62

Wikipedia在 C++11 final 修饰符上有以下示例:

struct Base2 {
    virtual void f() final;
};

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

我不明白引入虚函数并立即将其标记为 final 的意义。这只是一个不好的例子,还是有更多的例子?

4

10 回答 10

68

通常final不会在基类的虚函数定义中使用。final将由重写函数的派生类使用,以防止进一步的派生类型进一步重写函数。由于覆盖函数通常必须是虚拟的,这意味着任何人都可以在进一步的派生类型中覆盖该函数。final允许一个人指定一个覆盖另一个但本身不能被覆盖的函数。

例如,如果您正在设计一个类层次结构并需要覆盖一个函数,但您不希望类层次结构的用户也这样做,那么您可能会在派生类中将函数标记为 final。

于 2012-07-28T21:16:21.677 回答
11

对于要标记的函数,final它必须是virtual,即在 C++11 §10.3 段中。2:

[...] 为方便起见,我们说任何虚函数都会覆盖自身。

第 4 段:

如果某个类 B 中的虚函数 f 用 virt-specifier final 标记,并且在从 B 派生的类 D 中,函数 D::f 覆盖了 B::f,则程序是非良构的。[...]

即,final只需要与虚函数(或与阻止继承的类)一起使用。因此,该示例需要virtual用于它是有效的 C++ 代码。

编辑:要完全清楚:“点”询问了为什么甚至使用虚拟的问题。使用它的底线原因是(i)因为代码不会编译,以及,(ii)为什么在一个足够的情况下使用更多类使示例更复杂?因此,仅使用一个具有虚拟最终函数的类作为示例。

于 2012-07-28T21:06:35.983 回答
10

这对我来说似乎一点用都没有。我认为这只是演示语法的示例。

一种可能的用途是,如果您不希望 f 真正可覆盖,但您仍想生成一个 vtable,但这仍然是一种可怕的做事方式。

于 2012-07-28T20:30:43.373 回答
6

我不明白引入虚函数并立即将其标记为 final 的意义。

该示例的目的是说明如何final工作,它就是这样做的。

实际目的可能是查看 vtable 如何影响类的大小。

struct Base2 {
    virtual void f() final;
};
struct Base1 {
};

assert(sizeof(Base2) != sizeof(Base1)); //probably

Base2可以简单地用于测试平台细节,并且覆盖没有意义,f()因为它只是用于测试目的,所以它被标记为final. 当然,如果您这样做,那么设计中就有问题。我个人不会创建一个带有virtual函数的类来检查vfptr.

于 2012-07-28T20:42:54.330 回答
5

添加到上面的好答案 - 这是一个众所周知的 final 应用程序(非常受 Java 启发)。假设我们在 Base 类中定义了一个函数 wait(),并且我们希望在它的所有后代中实现一个 wait()。在这种情况下,我们可以将 wait() 声明为 final。

例如:

class Base { 
   public: 
       virtual void wait() final { cout << "I m inside Base::wait()" << endl; }
       void wait_non_final() { cout << "I m inside Base::wait_non_final()" << endl; }
}; 

这是派生类的定义:

class Derived : public Base {
      public: 
        // assume programmer had no idea there is a function Base::wait() 

        // error: wait is final
        void wait() { cout << "I am inside Derived::wait() \n"; } 
        // that's ok    
        void wait_non_final() { cout << "I am inside Derived::wait_non_final(); }

} 

如果 wait() 是纯虚函数,那将是无用的(并且不正确) 。在这种情况下:编译器会要求您在派生类中定义 wait()。如果你这样做,它会给你一个错误,因为 wait() 是最终的。

为什么最终函数应该是虚函数?(这也令人困惑)因为(imo)1)final的概念与虚函数的概念非常接近【虚函数有很多实现--final函数只有一种实现】,2)很容易实现final效果使用虚表。

于 2014-03-28T23:23:47.303 回答
5

在重构遗留代码时(例如,从母类中删除一个虚拟方法),这对于确保没有子类使用这个虚拟函数很有用。

// Removing foo method is not impacting any child class => this compiles
struct NoImpact { virtual void foo() final {} };
struct OK : NoImpact {};

// Removing foo method is impacting a child class => NOK class does not compile
struct ImpactChildClass { virtual void foo() final {} };
struct NOK : ImpactChildClass { void foo() {} };

int main() {}
于 2017-01-19T15:23:36.463 回答
3

这就是为什么您实际上可能选择在基类virtualfinal基类中声明函数的原因:

class A {
    void f();
};

class B : public A {
    void f(); // Compiles fine!
};

class C {
    virtual void f() final;
};

class D : public C {
    void f(); // Generates error.
};

标记的功能final 必须是virtual。标记函数final可防止您在派生类中声明具有相同名称和签名的函数。

于 2018-07-26T21:52:46.220 回答
1

而不是这个:

public:
    virtual void f();

我觉得写这个很有用:

public:
    virtual void f() final
        {
        do_f(); // breakpoint here
        }
protected:
    virtual void do_f();

主要原因是在分派到任何潜在的许多重写实现之前,您现在有一个断点位置。可悲的是(恕我直言),说“最终”也需要你说“虚拟”。

于 2016-09-27T20:04:21.693 回答
0

我发现了另一种情况,将虚函数声明为 final 很有用。此案例是SonarQube 警告列表的一部分。警告描述说:

在实例化覆盖成员函数的子类时,从构造函数或析构函数调用可覆盖的成员函数可能会导致意外行为。

例如:
- 按照约定,子类构造函数首先调用父类构造函数。
- 父类构造函数调用父成员函数,而不是子类中覆盖的那个,这对子类的开发人员来说是混乱的。
- 如果成员函数在父类中是纯虚函数,它会产生未定义的行为。

不合规代码示例

class Parent {
  public:
    Parent() {
      method1();
      method2(); // Noncompliant; confusing because Parent::method2() will always been called even if the method is overridden
    }
    virtual ~Parent() {
      method3(); // Noncompliant; undefined behavior (ex: throws a "pure virtual method called" exception)
    }
  protected:
    void         method1() { /*...*/ }
    virtual void method2() { /*...*/ }
    virtual void method3() = 0; // pure virtual
};

class Child : public Parent {
  public:
    Child() { // leads to a call to Parent::method2(), not Child::method2()
    }
    virtual ~Child() {
      method3(); // Noncompliant; Child::method3() will always be called even if a child class overrides method3
    }
  protected:
    void method2() override { /*...*/ }
    void method3() override { /*...*/ }
};

合规解决方案

class Parent {
  public:
    Parent() {
      method1();
      Parent::method2(); // acceptable but poor design
    }
    virtual ~Parent() {
      // call to pure virtual function removed
    }
  protected:
    void         method1() { /*...*/ }
    virtual void method2() { /*...*/ }
    virtual void method3() = 0;
};

class Child : public Parent {
  public:
    Child() {
    }
    virtual ~Child() {
      method3(); // method3() is now final so this is okay
    }
  protected:
    void method2() override { /*...*/ }
    void method3() final    { /*...*/ } // this virtual function is "final"
};
于 2017-03-31T11:05:54.180 回答
0

virtual+final用于一个函数声明中以使示例简短。

关于 and 的语法 Wikipedia示例通过引入包含 Base1和 Base2 包含(见下文)将更具表现力。virtualfinalstruct Base2 : Base1virtual void f();void f() final;

标准

参考N3690

  • virtualfunction-specifier可以作为的一部分decl-specifier-seq
  • final可以是一部分virt-specifier-seq

没有规则必须将关键字 virtual具有特殊含义的标识符 final一起使用。第 8.4 节,函数定义(注意 opt = 可选):

功能定义:

attribute-specifier-seq(opt) decl-specifier-seq(opt) declarator virt-specifier-seq(opt) 函数体

实践

在 C++11 中,您可以virtual在使用final. 这在 gcc >4.7.1、clang >3.0 和 C++11、msvc 上编译,...(请参阅编译器资源管理器)。

struct A
{
    virtual void f() {}
};

struct B : A
{
    void f() final {}
};

int main()
{
    auto b = B();
    b.f();
}

PS:cppreference 上的示例也没有在同一声明中将 virtual 与 final 一起使用。

PPS:同样适用于override.

于 2018-05-10T12:14:01.227 回答