C++对C的贡献之一是函数调用的动态绑定,C++把类型解析惹火我转交给编译器实现,从而减轻了程序员的负担,这是它的优点,但是另外一个方面,它带来负面作用,即开销过大。
3.1 虚函数的构造
1
2 3 4 5 6 7 |
void drawAllAnimals(ZooAnimal *pz)
//指向列表中第一个动物的指针。 { for(ZooAnimal *p = pz; p; p = p->next) { p->draw(); //由于draw是虚函数,是实现动态绑定。 } } |
1、构造函数必须初始化vptr。
2、虚函数是通过指针间接调用的,所以必须先得到指向虚函数表的指针,然后再获得正确的函数偏移量。
3、内联是在编译时决定的。编译器不可能把运行时才解析的虚函数设置为内联。
为了消除虚函数调用带来的问题,我们可以通过硬编码或者将它作为模板参数来传递,可以避免使用动态绑定。
3.2 模板和继承
我们想要开发一个线程安全的string类。Locker抽象基类将声明公共接口:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Locker
{ public: Locker() {} virtual ~Locker() {} virtual void lock() = 0; virtual void unlock() = 0; }; //CriticalSectionLock和MutexLock将从基类Locker派生出来 class CriticalSectionLock: public Locker { ... }; class MutexLock: public Locker { ... }; |
1、硬编程
可以从string类中派生出三个独立类:CriticalSectionString、MutexString和SemaporeString 。每个类实现各自名字所代表的同步机制。
2、继承
可以派生出一个单独的ThreadSafeString 类,它包含执行Locker对象的指针。在运行期间通过多态机制选择特定的同步机制。
3、模板
基于模板的string类,该类由Locker类型参数化后得到。
首先是硬编码的版本:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class CriticalSectionString:
public string
{ public: ... int length(); private: CriticalSectionLock cs; } int CriticalSectionString::length() { cs.lock(); int len = string::length(); cs.unlock(); return len; } |
尽管lock()和unlock()是虚函数,不过可以通过编译器进行静态的解析。
再然后是继承的版本:不能实现内联,所以带来性能损失。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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; } //使用方法如下 { CriticalSectionLock cs; ThreadSafeString csString( "hello", cs); //可以静态绑定 } |
最后看模板的实现:
基于模板的设计结合了两个方面的优点:重用和效率。ThreadSafeString是作为模板实现的,它的参数由Locker模板参数决定。实现如下:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
template<
class LOCKER>
class ThreadSafeString: public string { public: ThreadSafeString( const char *s): string(S) {} ... int length(); private: LOCKER lock; }; template < class LOCKER> inline int ThreadSafeString<LOCKER>::length() { lock.lock(); int len = string::length(); lock.unlock(); return len; } //我们可以实现静态绑定。 { ThreadSafeString <CriticalSectionLock> csString = "hello"; ... } |
3.3 要点
1、虚函数的代价在于无法内联函数调用,因为这些调用是在运行时动态绑定的。
2、模板比继承提供更好的性能,它把对类型的解析提前到编译期间,我们认为这是没有成本的。