4.4.2 虚函数
虚函数有两部分的开销:首先,与调用一个外联的非虚函数相比,调用一个外联的虚函数需要增加一些额外的机器指令。除非这个函数调用的频率非常高,否则这个效率的影响并不大。其次,也是更重要的影响因素,在大多数调用上下文中,现今的编译器并不能内联虚函数。为了说明这个问题,考虑4.4.2节开头给出的BSTree::size函数,我们现在把这个函数如下声明为虚函数:
- template<class T>
- class BSTree {
- virtual inline int size() const {
- return _size;
- }
- //...
- };
如果上下文可以很容易地静态确定是在BSTree对象调用这个函数BSTree::Size(而不是在其派生类的某个对象上调用),那么大多数编译器就可以内联BSTree::size函数:- BSTree<int> b;
- //...
- int sz = b.size(); //这里大多数编译器都可以内联。
另一方面,如果不能(或者很困难)决定BSTree::size是否在BSTree对象本身上调用,那么现有的大多数编译器就不能内联BSTree::size:- void f( const BSTree<int>& b) {
- int sz=b.size(); //大多数编译器都不能内联;这里的b是一个引用类型,b面的不是
- //...
- }
因此,基于现在的编译器技术,当我们需要把一个函数声明为虚函数时,我们就放弃了在大多数上下文内联这个函数的能力。关于是否内联一个函数或者把这个函数声明为虚函数问题,我们在表4.2给出了总结。
如果内联成员函数f是至关重要的,而用户又需要改写f,那就需要寻找某种重新设计并可以解决这个冲突的办法。幸运的是,在实际中这种冲突出现的几率很小。
如果内联成员函数f是至关重要的,并且用户不需要改写f,那么就应该把f声明为非虚内联函数。
如果内联不太重要,并且用户需要改写这个函数,那么就应该把f声明为非内联的虚函数。
最后,如果内联和改写都不是很重要,那么f就应该被声明为非虚函数,才能避免调用虚函数所花费的开销。
表4.2 成员函数是否应该声明为内联函数或者函数
|
用户是否需要改写< xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /> |
内联是否至关重要 |
|
是 |
否 |
|
是 |
重新设计程序库 |
非内联的虚函数 |
|
否 |
非虚内联函数 |
非虚函数,见表4.1 |
当然,要想知道哪些函数是用户需要改写的函数是非常困难的(见3.4.1节)。当有这方面疑问(即是否需要改写)的时候,我们就应该把函数声明为非虚函数;否则当真正需要改写函数时,用户肯定会不知所措(除非函数的源代码已经分发,用户可以得到源代码,见3.6节)。
几乎没有函数可以既声明为虚函数,又声明为内联函数。因为当这类函数的调用不能被内联的时候,将有可能在每个翻译单元中都生成一份这类函数的外联拷贝(见4.3.2节和4.4.2节)。