关于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. 不建议使用MI (Mission Impossible?), 在大多数情况下,它引入的问题,要比它解决的问题多的多的多。(纯个人观点)
2. 类层次结构越多,类的效率越低。
3. Virtual Inheritance的类成员访问效率要低于 Non-virtual Inheritance.
#include <iostream>
using namespace std;
class Base
{
public:
Base() { this->In(); }
virtual ~Base() { this->Out(); }
virtual void In() { cout << "Base::In" << endl;}
virtual void Out() { cout << "Base::Out" << endl;}
};
class Derived : public Base
{
public:
Derived() { this->In();}
virtual ~Derived() { this->Out();}
void In() { cout << "Derived::In" << endl;}
void Out() { cout << "Derived::Out" << endl;}
};
int main()
{
Base* p = new Derived;
p->Out();
delete p;
return 0;
}
输出结果:
Base::In
Derived::In
Derived::Out
Derived::Out
Base::Out
一目了然。不过指针 P 所指的实例,就要包含两张虚函数表,它的结构大概如下:
[
Base 的虚函数表
[func In] address1
[func Out] address2
]
[
Base 的data members
…
]
[
Derived 的虚函数表
[func In] address4
[func Out] address5
]
[
Derived 的data members
…
]
首先说说虚析构函数
构造函数是不可以为虚函数的,这个很明确哈
对于析构函数呢,因为对于多态中,子类可能有一些父类没有的东东要释放,所以虚析构函数是有必要的,也就是在继承中设计虚析构函数是有必要的。
那么,一个问题:在虚析构函数中调用虚函数(普通虚函数)会发生什么呢?
从过程来想:在建立一个对象时,会依次调用父构造函数-》子构造函数,同样,在析构时也会逆过来调用子析构函数-》父析构函数。那么在调用的时候,如果析构函数中对于虚函数还执行虚机制,就有可能已经执行过一个子对象的析构函数,又去调用他的函数,这样会很危险。所以在虚析构函数中,对于虚函数,只会执行目前最外一级的那个函数。
同样,在构造函数中,也不可以用虚机制去调用还没有建立的子对象的函数,所以都是这样调用的,调用本地函数,而不用虚机制。
本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/panlong1987/archive/2007/11/09/1875952.aspx