C++基本功和 Design Pattern系列(9) virtual 

2006-12-19 21:35 发布

引擎学习交流 /[各编程语言]
1936 0 0



======================================================

关于virtual 一般有3种用法: virtual function, pure virtual function, 和 virtual inheritance. 今天就分别来讲讲这几种virtual.

================ virtual function ================
virtual函数是在类里边的一种特殊的函数,至于具体的含义,相信大家都知道,我就不多说了。而virtual最基本的思想,就是OO语言中多态的体现。把函数从编译时的棒定,转移到运行时的动态绑定。

virtual的好处很多,比如能使程序结构更加清晰,代码的重复利用率更高, 但是也是有代价的。让我们来看下代码:

class Test {
    ...
    virtual void VirtualFunc(void) { cout<<"Test1"<<endl; };
};

class Test2 : public Test {
    ...
    virtual void VirtualFunc(void) { cout<<"Test2"<<endl; };
};

Test * pTest = new Test2();
pTest->VirtualFunc();

上面代码的输出结果是"Test2"。也就是说,虽然你是通过Test * 进行的调用,但真正执行的代码是你创建的Test2的VirtualFunc. 这样的效果是通过在编译的时候创建virtual pointer: _vptr 和 virtual table: _vtbl 来实现的(大部分编译器的实现是通过_vptr & _vtbl)。对于每个有virtual function的class,compiler创建一个_vtbl,用来记录所有的virtual function的地址。同时在所有这个class的instance(实例)里,都有一个_vptr,指向_vtbl。因此, pTest->VirtualFunc() 这段代码等同于:

    (*pTest->_vptr[X])()

其中X是VirtualFunc在_vtbl中对应的位置。(其实对于不同的compiler,都有不同的实现,不同的类层次结构,_vptr和_vtbl的数目也不一定相同。)

从上面的代码考虑,virtual 和 non-virtual的class对比,在性能上有3个方面受到影响:
    1. _vptr是在constructor中由compiler生成的代码隐性的初始化,占用了一定的时间。
    2. virtual function是通过指针间接调用的,比直接调用需要更多的时间,而且在有的情况下会导致CPU的指令缓存失效,从而浪费更多的时间。
    3. 由于virtual function是在运行时进行动态绑定的,所以无法进行inline.

对于第一条,如果是使用 virtual function,那是无可避免的,对于第2条,有通过显式的调用virtual function来提高速度,代码如下:

class Test2 : public Test {
    ...
    virtual void VirtualFunc(void) { cout<<"Test2"<<endl; };
    void VirtualFuncFast(void) { Test2::VirtualFunc(); };
};

由于Test2::VirtualFunc指定了调用函数的版本,所以是在编译时候就绑定了,免去了指针的间接调用过程,而且 VirtualFuncFast本身也是可以inline的。当然了,这样做也是有坏处的,就是VirtualFuncFast本身是Test2的函数,而不是Test的,所以不能通过Test的指针掉用。因此只有在确定是Test2的情况下,通过static_cast才能掉用 VirtualFuncFast.

同理,在一个virtual function里调用另外一个virtual function的时候,使用显试的调用也能提高一定的速度,比如:

class Test2 : public Test {
    ...
    virtual void VirtualFunc(void) { cout<<"Test2"<<endl; };
    virtual void VirtualFunc2(void) { Test2::VirtualFunc(); };
};


对于第3种情况,我们可以通过用Template代替Virtual Function来获得性能上的提高,举个简单的例子:

========== virtual 实现 ==========
class Base{
public:
    virtual void VirtualFunc(void);
};

class Derive1 : public Base{
public:
    virtual void VirtualFunc(void);
};

class Derive2 : public Base{
public:
    virtual void VirtualFunc(void);
};

class SomeClass {
public:
    void test(Base * pBase) { pBase->VirtualFunc(); };
};

// 用法
Test * pTest1 = new Derive1();
Test * pTest2 = new Derive2();
SomeClass Temp1.test(pTest1);
SomeClass Temp2.test(pTest2);

========== 对应的 Template 实现 ==========

class D1{
public:
    // inline here
    void VirtualFunc(void) {};
};

class D2{
public:
    // inline here
    void VirtualFunc(void) {};
};

template <class DCLASS>
class SomeClass {
public:
    // inline here
    void test( void ) { Temp.TestVirtualFunc(); };
private:
    DCLASS Temp;
};

// 用法
SomeClass <D1> Temp.test();
SomeClass <D2> Temp.test();


========== 如何选择 ==========

对于到底是使用 Virtual ,还是使用Template或者Virtual的优化形式, 在大多数情况下不难做出决定。具体选择的过程,只需要问问自己到底是程序的速度重要,还是其他重要,也就是,要在:速度,程序结构,灵活性,易用易维护性,代码的复杂度,代码大小这些中间做出选择。

如果速度排第一位,那么就使用template或者优化,如果其他排在速度的前面,应该尽量的使用virtual。


================ pure virtual function ================

pure virtual function主要是用在abstract class里。有pure virtual function的class,就是abstract class,是不能被实例化的。因此,pure virtual function的代码只有在指定的情况下才能被调用。 例如:

class Test {
    virtual void VirtualFunc(void) = 0 { cout<<"Test"<<endl; };
};


class Test2 : public Test {
    virtual void VirtualFunc(void) { Test::VirtualFunc(); };
};

================ virtual inheritance ================

其实Virtual Inheritance只有可能在Multiple Inheritance中使用。对于Muliple Inheritance, Aear是坚决反对反对再反对的。Aear个人认为,single inheritance是足够用的,Java就是最好的例子。 MI实在是太让人头疼了,所以Aear不会在这个系列中讲MI,这里只说说 Virtual Inheritance和普通的Inheritance的区别。

class Base {
    virtual ~Base();
};

class Derived1 : public Base {
};

class Derived2 : virtual public Base {
};

这里:

sizeof(Derived1) == 4
sizeof(Derived2) == 8

sizeof(Derived1) == 4 是因为 Derived1继承 Base以后,使用Derived1的 _vptr

sizeof(Derived1) == 8 是因为由于在所有的vritual inheritance里,Base作为基类在内存中只能出现一次。所以必须把Base和Derived2的附加部分单独对待。因此有2个_vptr,一个是Base的,一个是Derived2的。

virtual inheritance的直接结果就是大大增加了指令缓存失效的可能性,同时降低了类内部数据的访问速度。因为Base 和 Derived2的内部数据不再是放在连续的内存中了。如果virtual inheritance的层次越多,对运行速度的影响就越大。

所以Aear在这里极度不推荐MI和Virtual Inheritance.

================ virtual 的一些用法 ================
下面是virtual的一些用法。

========== virtual destructor ==========

这个比较简单,就是所有的Base Class,都应该是 public virtual destructor 或者是protected non-virtual destructor,例如:

class Base {
public:
    virtual ~Base();
};

或者

class Base {
protected:
    ~Base();
};

========== virtual constructor ==========

其实本没有virtual constructor,不过C++中有特殊的实现,实现类似virtual constructor 和 virtual copy constructor的。代码如下:

class Base {
    virtual Base * construct (void) { return new Base(); };
    virtual Base * clone(void) { return new Base(*this); };
};

class Derived : public Base {
    virtual Base * construct (void) { return new Derived(); };
    virtual Base * clone(void) { return new Derived(*this); };
};

========== pure virtual destructor ==========

如果我们想设置base class 为abstract class,而又只有一个destructor作为唯一的成员函数,可以把它设成为pure virtual

class Base {
public:
    virtual ~Base() = 0;
};

========== protected & private virtual ==========

对于virtual function是不是应该放在public里边,学术结论和现实中的代码往往不能统一。虽然很多人认为virtual function 不应该放在public里边,但是这样的设计经常会造成编码中的不方便,

实际上对与Java来说,protected 或 private 所有 virtual function,几乎是不太可能的。因此,Aear个人观点,是不是要protected & private virtual function,要根据情况而定。下面是个private virtual的例子:

class Base {
public:
    // this is interface
    void print(void) { output() };
private:
    virtual void output (void) { cout<<"Base"<<endl; };  
};

实际上这样的处理,要比public virtual速度上慢点。

========== 一些要点 ==========

1. Virtual Function不应该在Constructor的Destructor中掉用,因为那时候无法确定_vptr是否合法
2. 不建议使用MI (Mission Impossible?), 在大多数情况下,它引入的问题,要比它解决的问题多的多的多。
3. 类层次结构越多,类的效率越低。
4. Virtual Inheritance的类成员访问效率要低于 Non-virtual Inheritance.

楼主新帖

TA的作品 TA的主页
B Color Smilies

你可能喜欢

C++基本功和 Design Pattern系列(9) virtual 
联系
我们
快速回复 返回顶部 返回列表