我知道该final
关键字用于防止虚拟方法被派生类覆盖。但是,当我真的应该在方法中使用关键字时,找不到任何有用的示例。更甚者,使用虚拟方法感觉像是一种难闻的气味,因为它不允许程序员将来扩展类。final
virtual
final
我的问题是下一个:
当我真的应该final
在virtual
方法声明中使用时,是否有任何有用的情况?
我知道该final
关键字用于防止虚拟方法被派生类覆盖。但是,当我真的应该在方法中使用关键字时,找不到任何有用的示例。更甚者,使用虚拟方法感觉像是一种难闻的气味,因为它不允许程序员将来扩展类。final
virtual
final
我的问题是下一个:
当我真的应该final
在virtual
方法声明中使用时,是否有任何有用的情况?
Answers:
final
关键字的功能概述:假设我们有基类A
和派生类B
。函数f()
可以声明A
为virtual
,表示该类B
可以覆盖它。但是,然后类B
可能希望任何进一步派生的类B
都不能重写f()
。那是我们需要在中声明f()
as final
的时候B
。没有final
关键字,一旦我们将方法定义为虚拟方法,任何派生类都将可以随意重写它。该final
关键字用来杜绝这种自由。
你为什么会需要这样的事情的一个例子:假设类A
定义了一个虚函数prepareEnvelope()
和类B
重写它,实现它的呼叫到自己的虚拟方法的序列stuffEnvelope()
,lickEnvelope()
和sealEnvelope()
。类B
打算允许派生类重写这些虚拟方法以提供其自己的实现,但是类B
不想允许任何派生类重写prepareEnvelope()
并因此更改填充,舔,密封或忽略调用其中一个的顺序。因此,在这种情况下,类B
声明prepareEnvelope()
为final。
class B might wish that any class which is further derived from B should not be able to override f()
?在现实世界中是否存在这样的类B和方法f()并非常合适?
从设计角度来看,通常可以将事物标记为不变。以相同的方式,const
提供编译器保护并指示状态不应更改,final
可用于指示行为在继承层次结构中不应进一步更改。
例
考虑一种视频游戏,其中车辆将玩家从一个位置带到另一个位置。所有车辆应在出发前进行检查,以确保其行驶到有效的位置(例如,确保该位置的底座未损坏)。我们可以从使用非虚拟接口习惯用法(NVI)开始,以确保无论使用哪种车辆都可以进行此检查。
class Vehicle
{
public:
virtual ~Vehicle {}
bool transport(const Location& location)
{
// Mandatory check performed for all vehicle types. We could potentially
// throw or assert here instead of returning true/false depending on the
// exceptional level of the behavior (whether it is a truly exceptional
// control flow resulting from external input errors or whether it's
// simply a bug for the assert approach).
if (valid_location(location))
return travel_to(location);
// If the location is not valid, no vehicle type can go there.
return false;
}
private:
// Overridden by vehicle types. Note that private access here
// does not prevent derived, nonfriends from being able to override
// this function.
virtual bool travel_to(const Location& location) = 0;
};
现在让我们说我们的游戏中有飞行器,所有飞行器都需要并有一个共同点,那就是它们必须在起飞前经过机库内的安全检查。
在这里,我们可以final
用来保证所有飞行器都将通过此类检查,并传达飞行器的设计要求。
class FlyingVehicle: public Vehicle
{
private:
bool travel_to(const Location& location) final
{
// Mandatory check performed for all flying vehicle types.
if (safety_inspection())
return fly_to(location);
// If the safety inspection fails for a flying vehicle,
// it will not be allowed to fly to the location.
return false;
}
// Overridden by flying vehicle types.
virtual void safety_inspection() const = 0;
virtual void fly_to(const Location& location) = 0;
};
通过final
以这种方式使用,我们有效地扩展了非虚拟接口习惯用法的灵活性,以在继承层次结构(甚至是事后考虑到脆弱的基类问题)的范围内为虚拟函数本身提供统一的行为。此外,我们为自己准备了摆动空间,以作事后考虑对所有飞行器类型产生影响的集中更改,而无需修改现有的每个飞行器实现。
这是一个使用的示例final
。在某些情况下,对于虚拟成员函数的任何进一步重写都根本没有道理-这样做可能导致脆弱的设计并违反您的设计要求。
final
从设计/架构的角度来看,这是有用的。
从优化器的角度来看,它也是有用的,因为它为优化器提供了设计信息,使它可以对虚拟函数调用进行虚拟化(消除了动态调度开销,并且通常更重要的是,消除了调用者和被调用者之间的优化障碍)。
题
从评论:
为什么将final和virtual同时使用?
对于位于层次结构根部的基类,将函数声明为virtual
和都没有任何意义final
。这对我来说似乎很愚蠢,因为这会使编译器和人类读者都不得不跳过不必要的麻烦,而virtual
在这种情况下,只需避免完全避免就可以避免不必要的麻烦。但是,子类继承虚拟成员函数,如下所示:
struct Foo
{
virtual ~Foo() {}
virtual void f() = 0;
};
struct Bar: Foo
{
/*implicitly virtual*/ void f() final {...}
};
在这种情况下,是否Bar::f
显式使用virtual关键字Bar::f
都是虚拟函数。virtual
在这种情况下,关键字将变为可选。因此,它可能是有意义的Bar::f
被指定为final
,即使它是一个虚函数(final
可以只被用于虚拟函数)。
而且有些人可能希望从风格上明确指出它Bar::f
是虚拟的,例如:
struct Bar: Foo
{
virtual void f() final {...}
};
对我来说,在这种情况下(和和)在同一函数中同时使用virtual
和final
说明符是多余的,但是在这种情况下,这只是样式问题。某些人可能会发现在这里传达了一些有价值的信息,就像使用带有外部链接的函数声明一样(即使它是可选的,但缺少其他链接限定符)。virtual
override
virtual
extern
private
方法Vehicle
?你不是说protected
吗
private
不会扩展到覆盖(有点违反直觉)。虚拟函数的公共/受保护/私有说明符仅适用于调用者,不适用于重写器(简而言之)。无论其可见性如何,派生类都可以从其基类覆盖虚拟函数。
protected
可能更直观。我只想尽可能地降低可见度。我认为以这种方式设计语言的原因是,否则,在友谊的上下文之外,私有虚拟成员函数将毫无意义,因为如果访问说明在上下文中很重要,则只有类,而只有朋友可以覆盖它们而不是仅仅调用。
它可以进行很多优化,因为在编译时可能会知道调用了哪个函数。
小心扔掉“代码气味”一词。“最终”并非不可能扩展该类。双击“最终”一词,按退格键,并扩展课程。但是final是一个出色的文档,开发人员不希望您重写该函数,因此下一个开发人员应格外小心,因为如果final方法被这种方法覆盖,则该类可能会停止正常工作。
final
与virtual
曾在同一时间使用?
final
和override
。
final
确实可以为他们提供帮助