维基百科提供了以下关于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的意义所在。这是一个糟糕的例子,还是有更多的含义?
维基百科提供了以下关于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的意义所在。这是一个糟糕的例子,还是有更多的含义?
通常不会在基类虚函数的定义中使用final
关键字,而是由覆盖该函数的派生类使用final
,以防止进一步派生类型再次覆盖该函数。因为重载函数必须是虚函数,通常意味着任何人都可以在进一步的派生类型中覆盖该函数。final
允许您指定一个覆盖另一个函数但本身不能被覆盖的函数。
例如,如果您正在设计一个类层次结构并需要覆盖一个函数,但不希望允许类层次结构的用户这样做,则可以在派生类中将函数标记为final
。
由于评论中已经提到了两次,我想补充一下:
有些人认为基类声明一个非覆盖方法为final
的原因很简单,就是为了让在派生类中尝试定义该方法的人得到一个错误,而不是悄悄地创建一个'隐藏'基类方法的方法。
struct Base {
void test() { std::cout << "Base::test()\n"; }
};
void run(Base *o) {
o->test();
}
// Some other developer derives a class
struct Derived : Base {
void test() { std::cout << "Derived::test()\n"; }
};
int main() {
Derived o;
o.test();
run(&o);
}
Base
的开发者不希望Derived
的开发者这样做,并希望它产生一个错误。于是他们这样写:
struct Base {
virtual void test() final { ... }
};
使用这种声明方式Base::foo()
会导致Derived的定义产生错误,例如:
<source>:14:13: error: declaration of 'test' overrides a 'final' function
void test() { std::cout << "Derived::test()\n"; }
^
<source>:4:22: note: overridden virtual function is here
virtual void test() final { std::cout << "Base::test()\n"; }
^
你可以自行决定这个目的是否值得,但我想指出,声明函数为virtual final
并不是防止这种隐藏的完整解决方案。派生类仍然可以隐藏Base::test()
而不会引发所需的编译器错误:struct Derived : Base {
void test(int = 0) { std::cout << "Derived::test()\n"; }
};
无论`Base::test()`是`virtual final`还是非`virtual final`,这个`Derived`的定义都是有效的,并且代码`Derived o; o.test(); run(&o);`的行为完全相同。final override
。也许应该有一个风格警告,要求所有的final
函数像其他virtual
和override
风格警告一样被标记为override
。 - bames53final
接口方法,因为在C++中实现接口方法需要覆盖。这样一个无法实现的接口是没有用处的。 - bames53virtual
对我来说看起来像是混淆。 - bames53我觉得这个对我来说完全没用,我认为这只是一个示例,用来展示语法。
有一个可能的用途是,如果你不想让 f 被真正重载,但你仍然想生成一个虚函数表,但这仍然是一种可怕的做法。
dynamic_cast
就无法工作。但通常确保有虚函数表的方法是将析构函数声明为虚函数。 - Mark Ransom在上面的好回答中,这里有一个很出名的 final 应用(非常受 Java 启发)。假设我们在一个基类中定义了一个 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()是final的。
为什么一个final函数应该是虚函数?(这也很令人困惑) 因为(我认为)1)final的概念与虚函数的概念非常接近[虚函数有多种实现- final函数只有一种实现],2)使用虚表格很容易实现最终效果。
我不理解为什么要引入一个虚函数然后立即将其标记为final。
那个例子的目的是为了说明final
的作用,并且它确实做到了。
实际应用可能是看看虚表如何影响类的大小。
struct Base2 {
virtual void f() final;
};
struct Base1 {
};
assert(sizeof(Base2) != sizeof(Base1)); //probably
< p > Base2
可以用于测试平台特定的内容,而且没有必要覆盖 f()
,因为它只是用于测试目的,所以标记为 final
。当然,如果你这样做,说明设计存在问题。个人而言,我不会创建一个带有 virtual
函数的类来检查 vfptr
的大小。
在重构旧代码时(例如从母类中删除虚方法),这很有用,以确保没有子类正在使用此虚函数。
// 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() {}
以下是为什么您可能需要在基类中同时声明一个函数为 virtual
和 final
:
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
可以防止在派生类中声明具有相同名称和签名的函数。
virtual
,这样做也有效吗? - Omnifarious而不是这样:
public:
virtual void f();
I find it useful to write this:
public:
virtual void f() final
{
do_f(); // breakpoint here
}
protected:
virtual void do_f();
virtual
+ final
在一个函数声明中用于缩短示例。
关于virtual
和final
的语法,维基百科的示例可以通过引入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):
函数定义:
属性说明符序列(可选) 声明说明符序列(可选) 声明符 虚拟说明符序列(可选) 函数体
在C++11中,使用final
时可以省略virtual
关键字。这在gcc >4.7.1上编译,在clang >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();
}
附注:cppreference上的示例也没有在同一声明中使用virtual和final。
另外,对于override
也是如此。
我发现另一个情况,虚函数声明为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"
};
int final = 7;
如果你想了解 Stroustrup 对此的讲解,请点击这里。 - chris