派生类中虚函数调用的优化
Optimization of virtual function calls in derived class
在派生类中调用虚函数的最佳方法是什么,以便编译器可以内联或以其他方式优化调用?
的例子:
class Base {
virtual void foo() = 0;
};
class Derived: public Base {
virtual void foo() {...}
void bar() {
foo();
}
};
我希望bar()
中对foo()
的调用总是调用Derived::foo()
。这是我的理解,调用将导致虚函数表查找和编译器无法优化它,因为可能有另一个类继承从派生。
我可以显式地调用Derived::foo()
,但如果在派生中有许多虚拟函数调用,则会变得冗长。我也发现令人惊讶的是,我在网上找不到太多的材料来解决对我来说似乎是一个常见的情况(调用虚拟方法的"最终"派生类),所以我想知道我是否在这里滥用虚拟函数或过度优化。
这应该怎么做?停止过早地优化并坚持使用foo()
,接受它并使用Derived::foo()
,或者有更好的方法吗?
c++ 11包含final
关键字,该关键字"指定虚函数不能在派生类中被重写,或者不能从该类继承。"
如果在派生类中声明了final
,那么g++似乎能够优化虚函数调用。
我创建了以下测试:
virtualFunctions.h
#pragma once
class Base {
public:
virtual void foo();
virtual void bar();
virtual void baz();
int fooVar, barVar, bazVar;
};
class Derived: public Base {
public:
void test();
virtual void foo();
virtual void bar();
virtual void baz() final;
};
virtualFunctions.cpp
:
#include "virtualFunctions.h"
void Derived::test() {
foo();
Derived::bar();
baz();
}
void Derived::foo() {
fooVar = 101;
}
void Derived::bar() {
barVar = 202;
}
void Derived::baz() {
bazVar = 303;
}
我使用的是g++ 4.7.2,使用- 01生成的程序集包含:
_ZN7Derived4testEv:
.LFB0:
.loc 1 3 0
.cfi_startproc
.LVL3:
pushl %ebx
.LCFI0:
.cfi_def_cfa_offset 8
.cfi_offset 3, -8
subl $24, %esp
.LCFI1:
.cfi_def_cfa_offset 32
movl 32(%esp), %ebx ; Load vtable from the stack
.loc 1 4 0
movl (%ebx), %eax ; Load function pointer from vtable
movl %ebx, (%esp)
call *(%eax) ; Call the function pointer
.LVL4:
.loc 1 5 0
movl %ebx, (%esp)
call _ZN7Derived3barEv ; Direct call to Derived::bar()
.LVL5:
.loc 1 6 0
movl %ebx, (%esp)
call _ZN7Derived3bazEv ; Devirtualized call to Derived::baz()
Derived::bar()
和Derived::baz()
都被直接调用,而vtable被用于foo()
。
如果编译器可以静态地找出所使用的类型,它可能能够优化它并执行反虚拟化。
虚拟方法调用非常便宜。不久前,我读到一篇文章,指出与普通方法调用相比,开销大约是10%。这当然没有考虑到内联机会的缺失。
我也有一种感觉,这混合了接口和实现。我认为最好把它分成一个纯接口和一个实现类。
正如您自己所说,只有在极端罕见的情况下,您才应该考虑这样做对性能的影响。如果你用c++ 11编译,你可以声明Derived
和/或foo()/bar()
为final,编译器可能会内联它。
这个问题的答案是禁用动态分派,这可以通过限定来实现:
class Derived: public Base {
virtual void foo() {...}
void bar() {
Derived::foo(); // no dynamic dispatch
}
};
现在的问题是,这是否会对性能产生影响(在改变事物之前进行测量),以及这样做是否有意义。虚函数是派生类型的扩展点。如果你禁用动态调度,有人可能会创建MoreDerived
,实现foo
,并期望bar
调用MoreDerived::foo
,但如果你禁用动态调度,这将不会发生。
除非有一个非常好的,可衡量的,尝试微优化的理由,否则完全避免这个问题。如果您在分析器中运行代码,动态分派可能根本不会出现。
- 函数调用中参数的顺序重要吗
- 基于另一个成员参数将函数调用从类传递给它的一个成员
- 变量没有改变?通过向量的函数调用
- 在两个类中共享相同的函数调用,并在不需要时避免空实例化
- 是否有C++编译器选项允许激进地删除所有函数调用,并将参数传递给具有空体的函数
- 我知道函数调用中存在歧义.有没有办法调用foo()函数
- C++延迟后的优化器调用函数
- 编译器 虚拟函数调用的优化
- 根据全局日志级别优化日志函数调用
- C++:优化析构函数调用
- C++编译器是否优化重复的函数调用
- 下面的代码会被优化为一个函数调用吗
- C/C++中使用常量优化的函数调用
- 使用大型局部变量(C++)优化频繁调用的函数
- 优化外函数调用
- 为什么全局变量会给函数调用中的编译器优化带来麻烦
- 返回值优化和析构函数调用
- gcc是否会优化对相同变量的重复函数调用,并为每次调用提供相同的输出?
- 派生类中虚函数调用的优化
- 使用返回值优化get和函数调用周围的循环