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

问题描述 投票:48回答:10

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

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

c++ c++11 inheritance final virtual-functions
10个回答
54
投票

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

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


0
投票

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

关于virtualfinal的语法,通过引入含有struct Base2 : Base1的Base1和含有virtual void f();的Base2的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)函数体

实践

使用C ++ 11,您可以在使用virtual时省略final关键字。这可以在gcc> 4.7.1上编译,在msng上使用C ++ 11在clang> 3.0上编译,...(参见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


8
投票

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

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


7
投票

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

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

对于4:

如果某个类B中的虚函数f用virt-specifier final标记,并且在从B派生的类D中,函数D :: f会覆盖B :: f,则该程序是格式错误的。 [...]

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

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


4
投票

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

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

实际目的可能是看vtable如何影响班级的规模。

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

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

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


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。


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

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会阻止您在派生类中声明具有相同名称和签名的函数。


0
投票

而不是这个:

public:
    virtual void f();

我觉得写这个很有用:

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

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


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"
};
© www.soinside.com 2019 - 2024. All rights reserved.