一、基本概念
多态,是C++中一个很棒的特性——“无论发送消息的对象属于什么类,它们均发送具有同一形式的消息,对消息的处理方式可能随接手消息的对象而变”的处理方式被称为多态性。其实现是基于虚函数机制的。这是一种泛型技术。所谓泛型技术,说简单些就是使用不变的代码实现可变的算法。比如:模板、RTTI、虚函数。
虚函数,是一种成员函数,它可以在该类的派生类中被重新定义并被赋予另外一种处理功能。示例如下:
class Base
{
public:
...
virtual void Func1(int a){ cout << "Base::Func1()" << endl; }
};
class Derive : public Base
{
public:
...
virtual void Func1(int a){ cout << "Derive::Func1()" << endl; }
};
二、含有虚函数的类对象在内存中的结构
先来看一段示例代码:
class Base0
{
public:
...
virtual void Func0(int a){ cout << "Base0::Func0()" << endl; }
};
class Base1
{
public:
...
virtual void Func1(int a){ cout << "Base1::Func1()" << endl; }
};
class Base2
{
public:
...
virtual void Func1(int a){ cout << "Base2::Func1()" << endl; }
virtual void Func2(int a){ cout << "Base2::Func2()" << endl; }
};
class Derive1 : public Base1
{
public:
...
};
class Derive2 : public Base1
{
public:
...
virtual void Func1(int a){ cout << "Derive2::Func1()" << endl; }
};
class Derive3 : public Base1
{
public:
...
virtual void Func1(int a){ cout << "Derive3::Func1()" << endl; }
virtual void Func2(int a){ cout << "Derive3::Func2()" << endl; }
};
class Derive4 : public Base0, public Base1
{
public:
...
virtual void Func3(int a){ cout << "Derive4::Func3()" << endl; }
};
int main(int argc, char* argv[])
{
cout << "Size of Base0 = " << sizeof(Base0) << endl;
cout << "Size of Base2 = " << sizeof(Base2) << endl;
cout << "Size of Derive1 = " << sizeof(Derive1) << endl;
cout << "Size of Derive2 = " << sizeof(Derive2) << endl;
cout << "Size of Derive3 = " << sizeof(Derive3) << endl;
cout << "Size of Derive4 = " << sizeof(Derive4) << endl;
return 0;
}
上面代码的运行结果如下:
Size of Base0 = 4
Size of Base2 = 4
Size of Derive1 = 4
Size of Derive2 = 4
Size of Derive3 = 4
Size of Derive4 = 8
为什么会出现这个情况呢?编译器做了些什么呢?
实际上,在含有虚函数的类中,就必然存在一个虚函数表的指针vp,而在32位系统中,指针是占4个字节的。那么,上面的运行结果也就不是那么难以接受了。
另外,还有一些额外的发现:在单继承体系中,只有一张虚函数表vtbl,也就只有一个虚函数表指针vp;而在多继承体系中,却是有多个vtbl的,也就有多个vp。
PS:关于多重继承,请慎用!
三、什么情况下使用virtual
上面啰嗦了一堆,那么,到底什么情况下需要用到virtual关键字呢?
1.修饰成员函数
当virtual用来修饰成员函数时,就是它对多态性支持的最佳体现。
举个例子,有几个类:Dog、Pig、Turkey、Cat,他们都有相同的成员函数Eat(food)。当到了饭点,大家要吃饭时,编写的代码是如下的:
Dog dog;
Pig pig;
Turkey turkey;
Cat cat;
dog.Eat(food);
pig.Eat(food);
turkey.Eat(food);
cat.Eat(food);
动物种类少的时候,这样还看不出什么,如果有上千种动物呢?这么一行一行写下去,简直不敢想象。如此缺乏灵活性的代码,是优雅的C++程序员不可忍受的。
那么,如何解决呢?
我们可以添加一个类Animal,如下:
class Animal
{
public:
...
virtual void Eat(Food food) = 0;
};
再让上面的Dog、Pig、Turkey、Cat继承自Animal。这样,在使用时,代码就变成了:
Animal* arrayAnimal[MAX_ANIMAL_KINDS];
arrayAnimal[0] = new Dog;
arrayAnimal[1] = new Pig;
arrayAnimal[2] = new Turkey;
arrayAnimal[3] = new Cat;
for (int i=0; i<MAX_ANIMAL_KINDS; i++)
{
if (arrayAnimal[i])
arrayAnimal[i]->Eat(food);
}
细心的朋友应该发现了上面Animla类中的虚函数有所不同,
virtual void Eat(Food food) = 0;
这是纯虚函数,简而言之,纯虚函数为派生类提供了可覆盖的接口,但是基类中的版本决不会被调用。
含有一个或多个纯虚函数的类叫做抽象基类。抽象基类不能实例化。派生类只有重写这个接口才能实例化。
PS:友元函数、static函数、构造函数不能用virtual;普通成员函数和析构函数可以用virtual关键字修饰。
2.虚继承
虚继承是用来解决菱形继承带来的问题。示例代码如下:
class Base
{
public:
...
void func(){}
};
class Derive1 : public Base
{
public:
...
void func(){}
};
class Derive2 : public Base
{
public:
...
void func(){}
};
class Derive3 : public Derive1, public Derive2
{
public:
...
};
void main()
{
Derive3* p = new Derive3;
p->func();
delete p;
return 0;
}
编译时报错提示对func的访问不明确。如果Derive1和Derive2都用虚继承继承Base,则可以解决这个问题。
四、虚析构函数
虚析构函数可以确保在对象析构时,按照派生类->基类的顺序依次执行析构函数。
五、构造函数和析构函数中调用虚函数
如果在构造函数和析构函数中调用虚函数,则执行的是构造函数和析构函数所在类型定义的版本。