【发布时间】:2012-02-08 02:37:19
【问题描述】:
【问题讨论】:
-
为此我创建了一篇博文,因为当一个孩子问我“为什么要使用虚拟函数”时,我很难解释清楚。 nrecursions.blogspot.in/2015/06/…
标签: c++ virtual-functions
【问题讨论】:
标签: c++ virtual-functions
当您想要覆盖派生类的某种行为(读取方法)而不是为基类实现的行为并且您希望在运行时通过指向基类的指针这样做时,您可以使用虚函数。
典型的例子是当你有一个名为Shape 的基类和派生自它的具体形状(类)时。每个具体类都覆盖(实现一个虚拟方法)称为Draw()。
类层次结构如下:
下面的sn -p 展示了例子的用法;它创建了一个Shape 类指针数组,其中每个指针指向一个不同的派生类对象。在运行时,调用 Draw() 方法会导致调用该派生类覆盖的方法,并绘制(或渲染)特定的 Shape。
Shape *basep[] = { &line_obj, &tri_obj,
&rect_obj, &cir_obj};
for (i = 0; i < NO_PICTURES; i++)
basep[i] -> Draw ();
上面的程序只是使用指向基类的指针来存储派生类对象的地址。这提供了一种松散的耦合,因为如果随时添加新的具体派生类shape,则程序不必发生巨大变化。原因是实际使用(依赖)具体Shape 类型的代码段最少。
以上是著名的SOLID设计原则Open Closed Principle的一个很好的例子。
【讨论】:
virtual 而只是在Line 和Triangle 等子类中重新定义它们会怎样。那里会有什么不同?
当您需要以相同的方式处理不同的对象时,您可以使用虚函数。它被称为多态性。假设你有一些基类——比如经典的 Shape:
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 }
};
现在你可以拥有不同形状的向量:
vector<Shape*> shapes;
shapes.push_back(new Rectangle());
shapes.push_back(new Circle());
你可以像这样画出所有的形状:
for(vector<Shape*>::iterator i = shapes.begin(); i != shapes.end(); i++)
{
(*i)->draw();
}
通过这种方式,您可以使用一种虚拟方法 - draw() 绘制不同的形状。根据指针后面的对象类型的运行时信息选择正确的方法版本。
通知 当您使用虚函数时,您可以将它们声明为纯虚函数(就像在类 Shape 中,只需在方法 proto 之后放置“= 0”)。在这种情况下,您将无法使用纯虚函数创建对象实例,它将被称为抽象类。
还要注意析构函数之前的“虚拟”。如果您计划通过指向其基类的指针来处理对象,您应该将析构函数声明为虚拟的,因此当您为基类指针调用“delete”时,将调用所有析构函数链并且不会出现内存泄漏。
【讨论】:
想想动物类,从它派生的有猫、狗和牛。动物类有一个
virtual void SaySomething()
{
cout << "Something";
}
功能。
Animal *a;
a = new Dog();
a->SaySomething();
狗应该说“Bark”,猫应该说“Meow”,而不是打印“Something”。在此示例中,您看到 a 是 Dog,但有时您有一个动物指针,但不知道它是哪种动物。你不想知道它是哪种动物,你只是想让动物说点什么。所以你只要调用虚函数,猫会说“喵”,狗会说“吠”。
当然,SaySomething 函数应该是纯虚函数以避免可能的错误。
【讨论】:
Fox)并忘记覆盖 SaySomething 方法。如果你使用我提供的虚拟方法,Fox 实例会说“Something”,这是不对的。如果我们将 SaySomething 声明为纯虚拟,我们将无法实例化 Fox,包含 new Fox(...) 的代码将引发错误。这样,创建Fox 类的开发人员将被通知他/她在编译时的错误。编译时错误很好,因为它们不会浪费时间:)
你会使用虚函数来实现“多态性”,特别是当你有一个对象时,不知道实际的底层类型是什么,但知道你想对它执行什么操作,以及它的实现(它是如何做到的)因您实际拥有的类型而异。
基本上就是通常所说的“Liskov 替换原则”,以 Barbara Liskov 的名字命名,她在 1983 年左右谈到了这一点。
在您需要使用动态运行时决策的地方,在调用函数的代码被调用时,您不知道现在或将来可能通过它的类型,这是一个很好的模型。
但这不是唯一的方法。有各种各样的“回调”可以获取“blob”数据,并且您可能拥有依赖于传入数据中的标头块的回调表,例如消息处理器。为此,不需要使用虚函数,实际上您可能会使用一种仅使用一个条目(例如,只有一个虚函数的类)来实现 v-table 的方式。
【讨论】: