为啥对派生类使用基类指针

Posted

技术标签:

【中文标题】为啥对派生类使用基类指针【英文标题】:Why use base class pointers for derived classes为什么对派生类使用基类指针 【发布时间】:2012-03-14 14:41:40 【问题描述】:
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 工程师,我想知道这一点。如果我们可以避免基类指针,为什么还要使用虚函数?

【问题讨论】:

【参考方案1】:

在您的简单示例中,多态性的力量并不是很明显,但如果您将其扩展一点,它可能会变得更加清晰。

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();
      

 

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

【讨论】:

这可能是使用虚函数的最佳示例之一。谢谢! 出色的答案,但是当我已经知道我必须为所有这些类对象添加排放时,为什么我不能手动为“汽车”和“公共汽车”创建对象并添加他们通常?为什么需要基类类型指针。 假设如果我们不使用虚函数,使用基类指针指向派生类有什么好处?【参考方案2】:

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

    为什么对派生类使用基类指针? 这正是多态性的用途。它允许您统一对待对象,同时允许您有特定的实现。如果这让您感到困扰,那么我假设您应该问:为什么要使用多态性?

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

【讨论】:

【参考方案3】:

它实现了多态性。除非你有基类指针 指向派生对象,这里不能有多态性。

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

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

子类型多态性允许我们编写应用程序的内核 独立于我们希望操作的单个类型。而是我们 编程我们抽象的基类的公共接口 通过基类指针和引用。在运行时,实际 被引用的类型被解析并且相应的实例 调用公共接口。运行时分辨率 调用的适当函数称为动态绑定(默认情况下, 函数在编译时静态解析)。在 C++ 中,动态 绑定是通过一种称为虚拟类的机制来支持的 职能。通过继承和动态实现子类型多态性 绑定为面向对象编程提供了基础

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

而尝试使用不是从 Query 派生的对象的地址调用 eval() 导致编译时错误:

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

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

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

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

【讨论】:

最后一部分来自C++入门!拿到那本书并阅读多态性【参考方案4】:

你是对的,如果你有一个对象,你不需要通过指针来引用它。当对象将被销毁时,您也不需要虚拟析构函数。

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

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

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

【讨论】:

我认为他的问题是为什么使用基类指针而不是为什么使用虚拟析构函数

以上是关于为啥对派生类使用基类指针的主要内容,如果未能解决你的问题,请参考以下文章

为啥我可以通过指向派生对象的基类指针访问派生私有成员函数?

为啥基类指针指向基类中的纯虚方法,而不是派生类中的覆盖方法?

为啥可以从指向实例化基类对象的强制转换指针调用非静态派生类方法?

为啥我不能同时实例化对基类的引用作为派生类的指针?

为啥当从派生类访问基类的数据时它会返回废话(我认为是指针数据)

基类对派生类指针的引用