为什么要对派生类使用基类指针

Why use base class pointers for derived classes

本文关键字:基类 指针 派生 为什么      更新时间:2023-10-16
class base{
    .....
    virtual void function1();
    virtual void function2();
};
class derived::public base{
    int function1();
    int function2();
};
int main()
{
    derived d;
    base *b = &d;
    int k = b->function1() // Why use this instead of the following line?
    int k = d.function1(); // With this, the need for virtual functions is gone, right?
}

我不是CompSci工程师,我想知道这一点。如果我们可以避免基类指针,为什么要使用虚函数?

在你的简单示例中,多态性的力量并不明显,但如果你稍微扩展一下,它可能会变得更加清晰。

class vehicle{
      .....
      virtual int getEmission();
 }
 class car : public vehicle{
      int getEmission();
 }
 class bus : public vehicle{
      int getEmission();
 }
 int main()
 {
      car a;
      car b;
      car c;
      bus d;
      bus e;
      vehicle *traffic[]={&a,&b,&c,&d,&e};
      int totalEmission=0;
      for(int i=0;i<5;i++)
      {
          totalEmission+=traffic[i]->getEmission();
      }
 }

这使您可以循环访问指针列表,并根据基础类型调用不同的方法。基本上,它允许您编写代码,在编译时不需要知道子类型是什么,但代码无论如何都会执行正确的功能。

你是对的,如果你有一个对象,你不需要通过指针引用它。当对象将作为创建对象的类型销毁时,也不需要虚拟析构函数。

当您从另一段代码中获取指向对象的指针时,就会出现该实用程序,并且您并不真正知道派生最多的类型是什么。可以在同一基上生成两个或多个派生类型,并具有返回指向基类型的指针的函数。虚拟函数将允许您使用指针,而不必担心您使用的是哪个派生类型,直到需要销毁对象。虚拟析构函数将在您不知道它对应于哪个派生类的情况下销毁该对象。

以下是使用虚函数的最简单示例:

base *b = new derived;
b->function1();
delete b;

它来实现多态性。除非有基类指针指向派生对象,此处不能具有多态性。

派生类的主要功能之一是指向 派生类与指向其基类的指针类型兼容。 多态性是利用这种简单但 强大而通用的功能,带来了面向对象 充分发挥其潜力的方法论。

在C++中,存在一种特殊的类型/子类型关系,其中有一个基数 类指针或引用可以寻址其任何派生类 没有程序员干预的子类型。这种操纵能力 具有指针或对基类的引用的多个类型是 被称为多态性。

子类型多态性允许我们编写应用程序的内核 独立于我们希望操作的单个类型。相反,我们 为抽象基类的公共接口编程 通过基类指针和引用。在运行时,实际 解析所引用的类型,并解析 调用公共接口。运行时分辨率 要调用的相应函数称为动态绑定(默认情况下, 函数在编译时静态解析(。在C++,动态 通过称为类虚拟的机制支持绑定 功能。通过遗传和动态的亚型多态性 绑定为面向对象编程奠定了基础

继承层次结构的主要好处是我们可以编程 到抽象基类的公共接口,而不是到 以这种方式形成其继承层次结构的各个类型 保护我们的代码免受该层次结构中的更改。我们定义 eval((, 例如,作为抽象查询库的公共虚函数 .class。通过编写诸如 _rop->eval(); 用户代码不受我们查询语言的多样性和波动性的影响。这不仅允许添加、修订、 或删除类型而无需更改用户程序,但 使新查询类型的提供程序不必重新编码行为 或层次结构本身中所有类型通用的操作。这是 由遗传的两个特殊特征支持:多态性 和动态绑定。当我们谈到C++内的多态性时,我们 主要是指指针或基类引用的能力 以寻址其任何派生类。例如,如果我们定义一个 非成员函数 eval(( 如下所示,//pquery 可以寻址任何 派生自查询的类 void eval( const Query *pquery ) { pquery->eval(); } 我们可以合法地调用它,传入任何对象的地址 四种查询类型:

    int main() 
{ 
AndQuery aq;
 NotQuery notq; 
OrQuery *oq = new OrQuery; 
NameQuery nq( "Botticelli" ); // ok: each is derived from Query 
// compiler converts to base class automatically 
eval( &aq );
 eval( &notq ); 
eval( oq ); 
eval( &nq );
 } 

而尝试使用非从查询派生的对象的地址调用 eval(( 导致编译时错误:

int main()
 { string name("Scooby-Doo" ); // error: string is not derived from Query 
eval( &name); 
}

在 eval(( 中,执行 pquery->eval((; 必须调用 基于实际类的适当 eval(( 虚拟成员函数 对象查询地址。在前面的示例中,依次进行查询 地址 AndQuery 对象、NotQuery 对象、OrQuery 对象, 和一个名称查询对象。在执行期间的每个调用点 在我们的程序中,Pquery 解决的实际类类型是 确定,并调用相应的 eval(( 实例。动态 绑定是实现这一目标的机制。 在面向对象的范式中,程序员操作一组绑定但无限的类型集的未知实例。(这套 类型受其继承层次结构的约束。然而,从理论上讲,有 对层次结构的深度和广度没有限制。在C++这个 通过基类操作对象来实现 仅限指针和引用。在基于对象的范式中, 程序员 操作在编译点完全定义的固定单一类型的实例。虽然 对象的多态操作要求对象 通过指针或引用访问,操作 C++中的指针或引用本身不一定会产生 在多态性中。例如,考虑

// no polymorphism 
  int *pi; 
// no language-supported polymorphism 
  void *pvi; 
// ok: pquery may address any Query derivation
  Query *pquery;

在C++,多态性 仅存在于单个类层次结构中。类型的指针 void* 可以被描述为多态的,但它们没有明确的 语言支持 — 也就是说,它们必须由程序员管理 通过显式转换和某种形式的判别器来跟踪 要解决的实际类型。

你似乎问了两个问题(在标题和结尾(:

  1. 为什么对派生类使用基类指针?这就是多态性的用法。它允许您统一处理对象,同时允许您进行特定的实现。如果这困扰你,那么我想你应该问:为什么是多态性?

  2. 如果我们可以避免基类指针,为什么要使用虚拟析构函数?这里的问题是,你不能总是避免基类指针来利用多态性的力量。