最终虚拟功能的重点是什么?

时间:2012-07-28 20:27:24

标签: c++ c++11 inheritance final virtual-functions

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

我不明白引入虚拟功能并立即将其标记为最终功能。这只是一个不好的例子,还是有更多的呢?

10 个答案:

答案 0 :(得分:52)

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

例如,如果您正在设计类层次结构并需要覆盖函数,但您不希望允许类层次结构的用户执行相同操作,那么您可能会在派生中将函数标记为final类。

答案 1 :(得分:8)

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

一种可能的用法是,如果你不希望f真正被重写,但你仍然想要生成一个vtable,但这仍然是一种可怕的做事方式。

答案 2 :(得分:7)

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

  

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

和第4段:

  

如果某个类B中的虚函数f用virt-specifier final标记,并且在D类中派生自   B函数D :: f重写B :: f,程序格式错误。 [...]

即,final仅需要与虚函数(或使用类来阻止继承)一起使用。因此,该示例需要virtual才能使其成为有效的C ++代码。

编辑:要完全明确:“要点”询问有关虚拟甚至使用的问题。使用它的底线原因是(i)因为代码不会编译,而且,(ii)为什么在一个人满足时使用更多的类使示例更复杂?因此,使用具有虚拟最终函数的一个类作为示例。

答案 3 :(得分:4)

  

我不明白引入虚拟功能并立即将其标记为最终功能。

该示例的目的是说明final的工作原理,并确实如此。

实用目的可能是为了了解vtable如何影响班级的规模。

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

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

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

答案 4 :(得分:2)

添加上面的好答案 - 这是一个着名的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的概念非常接近虚函数的概念[虚函数有很多实现 - 最终函数只有一个实现],2)很容易使用vtables实现最终效果。

答案 5 :(得分:2)

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

// 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() {}

答案 6 :(得分:1)

这就是为什么您可能实际上选择在基类中同时声明函数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可以防止您在派生类中声明具有相同名称和签名的函数。

答案 7 :(得分:0)

而不是:

public:
    virtual void f();

我觉得写这个很有用:

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

主要原因是您在调度到任何潜在的多个重写实现之前,现在只有一个断点位置。可悲的是(恕我直言),说&#34;最后&#34;还要求你说&#34;虚拟。&#34;

答案 8 :(得分:0)

我发现了另一种情况,虚拟函数可用于声明为final。此案例是SonarQube list of warnings的一部分。警告说明说:

  

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

     

例如:
   - 通过契约,子类类构造函数通过调用父类构造函数开始    - 父类构造函数调用父成员函数而不是子类中重写的函数,这对子类的开发人员来说很困惑。
   - 如果成员函数在父类中是纯虚函数,则它可以产生未定义的行为。

不合规代码示例

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

答案 9 :(得分:0)

在一个函数声明中使用

virtual + final来缩短示例。

关于virtualfinal语法,通过在包含struct Base2 : Base1和Base2的Base1中引入virtual void f();,Wikipedia示例会更具表现力包含void f() final;(见下文)。

标准

参考N3690

  • virtual function-specifier可以成为decl-specifier-seq
  • 的一部分
  • final可以成为virt-specifier-seq
  • 的一部分

没有规则必须同时使用关键字 virtual和具有特殊含义的标识符 final。第8.4节,函数定义(heed opt = optional):

  

函数的定义:

     

attribute-specifier-seq(opt)decl-specifier-seq(opt)声明符virt-specifier-seq(opt)function-body

实践

使用C ++ 11,您可以在使用virtual时省略final关键字。这在gcc&gt; 4.7.1上编译,在clang&gt; 3.0上使用C ++ 11,在msvc上编译...(见compiler explorer)。

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

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

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

PS:example on cppreference也不会在同一声明中使用virtual和final。

PPS:同样适用于override