1. 虚函数的性能损耗
a. 构造函数必须初始化vptr(指向虚函数表的指针)
b. 虚函数是通过指针间接调用的,所以必须先得到指向虚函数表的指针,然后再获得正确地函数偏移量
c. 内联是编译时决定的,编译器不可能把运行时才解析的虚函数设置为内联
总结:客观看待C++,前两项不能算是性能损失。
对于a,因为在构造函数中设置vptr的代价与在子类的实现中初始化成员了变量的代价相当。
对于b,简介调用函数的代价,因为必须区分是哪一个子类调用该函数,所以是和switch语句的代价相当。
总结:为了消除虚函数调用, 必须允许编译器在编译期间就解析函数的绑定。通过对类选择进行硬编码或者将它作为模板参数来传递,可以避免动态绑定。(硬编码:将可变变量用一个固定值来代替的方法,这里是指直接在子类内部实现该方法)
基于模板的设计把解析步骤从运行期间提前到编译期间,因此模板提高了性能。示例如下:
2. 实现线程安全的string类。被并发线程安全的使用:3种方法
class Locker{
public:
Locker() {}
virtural ~Locker(){}
virtural void lock() = 0;
virtural void unlock() = 0;
}
class CriticalSectionLock : public Locker { ... }; //这里会实现虚函数
class MutexLock : public Locker { ... };
a. 硬编码:从string类中派生出三个独立的子类,每个类实现各自名字所代表的同步机制。
例:class CriticalSectionString : public string{
public :
...
int length();
private:
CriticalSectionLock cs;
}
int CriticalSectionString:: length(){
cs.lock();
int len = string::length();
cs.unlock();
return len;
}
虽然从父类string中得到了字符串的实际长度,但是将最后一步操作放在临界区内以保证计算的完整性。
这种设计在性能上具有优势,尽管lock()和unlock()方法是虚函数,但是它们可以被适当的编译器静态的解析。因此编译器可以绕过动态绑定来选择正确的lock()和unlock()方法。同时编译器可以内联这两个方法,不足在于重用性较低,需要为每种同步机制编写各自的string类。
b. 继承
把所选择的同步机制作为构造函数的参数
class ThreadSafeString : public string {
public:
ThreadSafeString (const char *s, Locker *lockPtr): string(s), pLock(LockPtr){}
...
int length();
private:
Locker *pLock;
}
int ThreadSafeString::length(){
pLock->lock();
int len = string::length();
pLock->unlock();
return len;
}
这个类根据传递给构造函数的Locker指针使用所有可用的同步策略,可以按下面的方式使用临界区:
{
CriticalSectionLock cs;
ThreadSafeString csString("Hello", &cs);
...
}
或者可以这样使用互斥锁:
{
MutexLock mtx;
ThreadSafeString csString("Hello", &mtx);
...
}
这一种更加简洁,但是带来了性能损失,虚函数调用lock() 和unlock()尽在执行期间解析,因此不能对他们内联。
c. 模板
基于模板的设计集合两方面有点:重用和效率。 ThreadSafeString 是作为模板实现的,它的参数由Locker模板参数决定。
template
class ThreadSafeString : public string{
public:
ThreadSafeString (const char *s ): string(s) {}
...
int length();
private:
LOCKER lock;
};
template
inline
int ThreadSafeString
lock.lock();
int len = string::length();
lock.unlock();
return len;
}
如果我们想使用临界区保护策略,则
{
ThreadSafeString
...
}
或者使用它互斥:
{
ThreadSafeString
...
}
这种设计避免了对lock和unlock的虚函数调用。 ThreadSafeString 声明在实例化模板是选择特定的同步类型,如同硬编码一样,
编译器可以解析这两个虚函数调用并且内联他们。
3. 总结:
a、虚函数的代价在于无法内联函数调用
b、模板比继承提供更好的性能,应为他对类型的解析在编译阶段。