虚函数效率和'final'关键字
Virtual function efficiency and the 'final' keyword
考虑一个 class Foo
包含函数 Foo::fn
声明如下的程序:
virtual void fn();
和 Foo
的子 class 称为 Bar
。将这样声明 Bar::fn
:
virtual void fn() override final;
导致调用 Bar
中的 fn
或 Bar
的子 class 更有效,或者它会保持子 class Bar
的 es 从覆盖 fn
?如果使用 final
使调用更高效,定义 Bar::fn
使其功能与 Foo::fn
完全相同的最简单、最有效的方法是什么?
我从来不关心 vtable 的大小。它通常相对较小,每个 class 声明只有一个。更麻烦的是在 class 个实例中占用了额外的 space 个,因为除了单身人士外, class 个实例通常很多。因此,以某种方式向 class 添加额外的元素肯定会影响内存量。如果 vtable 太大真的困扰你,那么做一些重新设计,这样就没有那么多不同的虚拟成员函数(可能将 class 层次结构分成几个 classes)或更少的派生classes。但实际上,即使您有数百个 classes,每个都有一百个虚拟成员函数,它仍然相对较小 - 200 个 classes 和 100 个成员将占用每个条目 20000 * 8 个字节 [64位架构] -> 160KB。当然是 20000 个函数 [是的,理论上,每个派生只需要一个新函数 class 就需要一个新的 vtable,但这是一个相当愚蠢的设计,在现实中不太可能]
final
关键字的目的是确保您不会从中进一步派生 - 这很有用,例如,如果您有一个基本的 class 层次结构,其中某些特定功能不应该是 "changed"。比如说你有:
class user_base
{
public:
virtual bool check_password(); {... magical code .. };
virtual bool is_super_user() = 0;
};
class superuser : public user_base
{
public:
virtual bool check_password() final
{ .... magical code ...
... extra checks to ensure no remote login...
}
virtual bool is_super_user() final { return true; }
};
class user : public user_base
{
public:
virtual bool is_super_user() final { return false; }
};
您必须花点时间确保 user_base
不被用作 fake_super_user
的基础 class,当然,还有其他重要的安全措施这样的设计有问题,但它给了你一些想法。
如果 fn
在 Bar
中定义为 final
,编译器可以通过指针或对 Bar
的引用静态地调度对 fn
的调用,因为它知道 Bar::fn
是最终的替代者。比如这个程序片段:
struct Foo {
virtual void fn();
};
struct Bar : Foo {
void fn() final override;
};
void with_foo(Foo& o) { o.fn(); }
void with_bar(Bar& o) { o.fn(); }
编译为 (See gcc.godbolt.org for details):
with_foo(Foo&):
subq , %rsp
movq (%rdi), %rax
call *(%rax)
addq , %rsp
ret
with_bar(Bar&):
subq , %rsp
call Bar::fn()
addq , %rsp
ret
with_foo
中的调用通过 vtable 动态调度(call *(%rax)
是间接调用),但 with_bar
中的调用静态调度到 Bar::fn()
。
在不改变行为的情况下使 Bar::fn
成为 Foo::fn
的最终替代者的最简单方法是将其定义为静态调用 Foo::fn
:
struct Bar : Foo {
void fn() final override { Foo::fn(); }
};
考虑一个 class Foo
包含函数 Foo::fn
声明如下的程序:
virtual void fn();
和 Foo
的子 class 称为 Bar
。将这样声明 Bar::fn
:
virtual void fn() override final;
导致调用 Bar
中的 fn
或 Bar
的子 class 更有效,或者它会保持子 class Bar
的 es 从覆盖 fn
?如果使用 final
使调用更高效,定义 Bar::fn
使其功能与 Foo::fn
完全相同的最简单、最有效的方法是什么?
我从来不关心 vtable 的大小。它通常相对较小,每个 class 声明只有一个。更麻烦的是在 class 个实例中占用了额外的 space 个,因为除了单身人士外, class 个实例通常很多。因此,以某种方式向 class 添加额外的元素肯定会影响内存量。如果 vtable 太大真的困扰你,那么做一些重新设计,这样就没有那么多不同的虚拟成员函数(可能将 class 层次结构分成几个 classes)或更少的派生classes。但实际上,即使您有数百个 classes,每个都有一百个虚拟成员函数,它仍然相对较小 - 200 个 classes 和 100 个成员将占用每个条目 20000 * 8 个字节 [64位架构] -> 160KB。当然是 20000 个函数 [是的,理论上,每个派生只需要一个新函数 class 就需要一个新的 vtable,但这是一个相当愚蠢的设计,在现实中不太可能]
final
关键字的目的是确保您不会从中进一步派生 - 这很有用,例如,如果您有一个基本的 class 层次结构,其中某些特定功能不应该是 "changed"。比如说你有:
class user_base
{
public:
virtual bool check_password(); {... magical code .. };
virtual bool is_super_user() = 0;
};
class superuser : public user_base
{
public:
virtual bool check_password() final
{ .... magical code ...
... extra checks to ensure no remote login...
}
virtual bool is_super_user() final { return true; }
};
class user : public user_base
{
public:
virtual bool is_super_user() final { return false; }
};
您必须花点时间确保 user_base
不被用作 fake_super_user
的基础 class,当然,还有其他重要的安全措施这样的设计有问题,但它给了你一些想法。
如果 fn
在 Bar
中定义为 final
,编译器可以通过指针或对 Bar
的引用静态地调度对 fn
的调用,因为它知道 Bar::fn
是最终的替代者。比如这个程序片段:
struct Foo {
virtual void fn();
};
struct Bar : Foo {
void fn() final override;
};
void with_foo(Foo& o) { o.fn(); }
void with_bar(Bar& o) { o.fn(); }
编译为 (See gcc.godbolt.org for details):
with_foo(Foo&):
subq , %rsp
movq (%rdi), %rax
call *(%rax)
addq , %rsp
ret
with_bar(Bar&):
subq , %rsp
call Bar::fn()
addq , %rsp
ret
with_foo
中的调用通过 vtable 动态调度(call *(%rax)
是间接调用),但 with_bar
中的调用静态调度到 Bar::fn()
。
在不改变行为的情况下使 Bar::fn
成为 Foo::fn
的最终替代者的最简单方法是将其定义为静态调用 Foo::fn
:
struct Bar : Foo {
void fn() final override { Foo::fn(); }
};