虚函数
前言
性能优化的目的是为了让程序变得高效,但同时也不能丧失程序的可维护性和可扩展性。
本章总结主要是关于虚函数方面的性能优化的要点
一、虚函数的构造
虚函数想必大家都比较了解。虚函数的引入为c++提供了多态性,为实现面向对象编程奠定了基础。但是仔细分析一下,虚函数的额引入必然也会带来一些开销。比如,虚函数实现的一个重要的指针vptr,正是这个指针使得父类能够正确的找到子类继承的函数;其次,我们必须先得到指向函数表的指针,然后访问正确的函数偏移量;最后,比较容易被忽略的一点是虚函数是在运行时进行调用的,因此不能使用内联。
但是文中也明确指出,前两条并不能算是虚函数带来的性能损失,因为第一条是为了动态绑定才产生的损耗,但是试想一下,如果使用虚函数这种动态绑定的方式,想要实现类似的功能也会需要花费其他的性能;第二条的话其实也可以看作是一个switch case的损耗,这种其他的实现方式也会有。因此真正的性能损失就是内联函数带来的损耗。而不能内联的损耗也需要依赖函数的复杂度以及调用的频率。另外,在现实的工程中可能还会出现虚继承以及多重继承这样的操作,这样就会增加vptr的间接调用,但是这样的影响也是比较微小,一般情况下也不会有显著的代码影响。
因此使用虚函数之前如果能了解到以上这些影响点就能更好的去判断是否使用虚函数的方式实现自己想要的功能。
二、模版和继承
函数的动态绑定是继承带来的结果,消除动态绑定的一种方法就是使用模板的方式,这样的话就可以将绑定的过程从运行提前到编译,降低了运行时带来这方面的消耗。
书中提到了这样一个例子。开发一个线程安全的类,为了使的方案变得灵活需要在某种场景能够选择到最适合的方案。先会有一个Locker的基类,之后的具体场景需要自己适配。
class Locker {
Locker(){}
virtual ~Locker(){}
virtual void lock() = 0;
virtual void unlick() = 0;
};
这个大致的方案就是有这样三种:1. 使用硬编码的方式,Locker派生出几种不同的mutex满足不同的需求,实现由每个类的内部自己实现;2. 使用继承,只派生出一个类,在运行时动态的选择使用的方式;3. 使用模板,创建一个有Locker类型参数化的模板类。
// section1
class MutexString public string {
...
private:
MutexLock pLock;
};
// section2:
class MutexString public string {
MutexString(const char* s, Locker* lock)
: string(s)
, pLock(lock)
{}
...
private:
Locker pLock;
};
// section3:
template <class LOCKER>
class ThreadSafeString : public string {
public:
ThreadSafeString(const char* s) : string(s) {}
...
private:
LOCKER lock;
};
// 调用方式
ThreadSafeString<MutexLock> string = "aaa";
第三种设计的方式有效的避开了lock和unlock的虚函数调用。因为ThreadSafeString可以选择一种Lock的方式填入class,这样运行时class的lock和unlock函数是确定的,因此可以减少虚函数绑定的过程。我们看到将计算从运行推到编译,这样就可以对性能产生积极的作用。
总结
本章主要讲了两点,一个是虚函数的代价起源于无法使用内联函数,函数的调用在运行时才确定,一定程度上提升了资源消耗;还有一个就是解决虚函数的方法,就是使用模板。