关于C++:为什么要使用虚拟函数?

Why use virtual functions?

本问题已经有最佳答案,请猛点这里访问。

Possible Duplicate:
Can someone explain C++ Virtual Methods?

我有一个关于C++虚拟函数的问题。

为什么以及何时使用虚拟函数?有人能给我一个虚拟功能的实时实现或使用吗?


如果要重写派生类的某个行为(读取方法),而不是为基类实现的行为(读取方法),并且要在运行时通过指向基类的指针来执行,则可以使用虚拟函数。

经典的例子是当您有一个名为Shape的基类和从中派生的具体形状(类)时。每个具体的类重写(实现一个虚拟方法),称为Draw()

类层次结构如下:

Class hierarchy

下面的代码片段显示了示例的用法;它创建了一个Shape类指针数组,其中每个指针指向一个不同的派生类对象。在运行时,调用Draw()方法会导致调用该派生类重写的方法,并绘制(或呈现)特定的Shape

1
2
3
4
Shape *basep[] = { &line_obj, &tri_obj,
                   &rect_obj, &cir_obj};
for (i = 0; i < NO_PICTURES; i++)
    basep[i] -> Draw ();

上面的程序只是使用指向基类的指针来存储派生类对象的地址。这提供了一个松耦合,因为如果随时随地添加一个新的具体派生类Shape,那么程序就不必大幅更改。原因是实际使用(依赖)具体的Shape类型的代码段最少。

以上是著名固体设计原理的开闭原理的一个很好的例子。


当需要以相同的方式处理不同的对象时,可以使用虚拟函数。这叫做多态性。让我们假设您有一些基本类-类似于经典形状:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    class Shape
    {
        public:
           virtual void draw() = 0;
           virtual ~Shape() {}
    };

    class Rectange: public Shape
    {
        public:
            void draw() { // draw rectangle here }
    };


    class Circle: public Shape
    {
        public:
           void draw() { // draw circle here }
    };

现在您可以得到不同形状的向量:

1
2
3
    vector<Shape*> shapes;
    shapes.push_back(new Rectangle());
    shapes.push_back(new Circle());

你可以这样画所有的形状:

1
2
3
4
    for(vector<Shape*>::iterator i = shapes.begin(); i != shapes.end(); i++)
    {
          (*i)->draw();
    }

通过这种方式,您可以使用一个虚拟方法绘制不同的形状-draw()。根据指针后面对象类型的运行时信息选择正确的方法版本。

通知使用虚函数时,可以将其声明为纯虚函数(如在类形状中,只需将"=0"放在方法proto之后)。在这种情况下,您将无法使用纯虚拟函数创建对象的实例,它将被称为抽象类。

在析构函数之前还要注意"虚"。如果计划通过指向对象基类的指针来处理对象,则应声明析构函数为虚拟的,因此当为基类指针调用"delete"时,将调用所有析构函数链,并且不会出现内存泄漏。


想想动物类,从中衍生出猫、狗和牛。动物类有一个

1
2
3
4
virtual void SaySomething()
{
    cout <<"Something";
}

功能。

1
2
3
Animal *a;
a = new Dog();
a->SaySomething();

狗应该说"汪汪",猫应该说"喵",而不是印刷"某物"。在本例中,您看到a是一只狗,但有时您有一个动物指针,不知道它是哪种动物。你不想知道它是哪种动物,你只想让它说些什么。所以你只需要调用虚拟功能,猫会说"喵",狗会说"汪汪"。

当然,SaySomething函数应该是纯虚拟的,以避免可能的错误。


您将使用一个虚拟函数来实现"多态性",特别是在您有对象的地方,不知道实际的底层类型是什么,但是知道您想要对它执行什么操作,并且这个(它是如何实现的)的实现取决于您实际拥有的类型。

从本质上讲,这就是通常所说的"里斯科夫替代原则",以1983年左右谈论这一问题的芭芭拉·里斯科夫的名字命名。

在需要使用动态运行时决策的地方,在调用调用函数的代码时,无论现在还是将来,您都不知道哪些类型可以通过它,这是一个好的模型。

但这不是唯一的办法。有各种各样的"回调"可以接受"blob"数据,并且您可能有依赖于传入数据中的头块的回调表,例如消息处理器。为此,不需要使用虚函数,实际上,您可能会使用的是一种方法,即V-Table只使用一个条目实现(例如,只使用一个虚函数的类)。