为什么我们需要一个超类的指针指向子类的对象?

Why do we need a pointer of superclass to point to a object of a subclass?

本文关键字:指针 子类 对象 超类 一个 我们 为什么      更新时间:2023-10-16

我现在正在学习c++,我读了很多关于使用超类的指针指向子类对象的材料,特别是在(纯)虚拟类的情况下。因为我没有太多的经验,有没有人能帮我理解为什么我们需要这样做?非常感谢!

你不需要。如果确实需要,可以使用指向派生类型的指针。

Liskov替换原则说,我们应该总是能够在期望基类型的地方使用派生类型。其思想是派生类型不应该比它的基类有更多的限制。这样,派生的实际上是一个基类型,并且可以在任何需要使用基类型的地方使用。基类型定义接口,派生类型应满足相同的接口。派生类型可以扩展接口,如果它喜欢的话。

函数应该接受的指针类型取决于您希望能够接受的类型。例如,如果您有两个Widget, ButtonList的层次结构,那么如果您的函数乐于接受任何类型的Widget,它应该接受Widget*。但是,如果函数特别需要Button,则应该使用Button*。这样做的原因是该函数可能需要一些只有Button才能提供的功能。

当通过指针调用成员函数并且编译器看到该函数是virtual时,编译器确保使用对象的动态类型来确定调用哪个函数。也就是说,假设您有一个Widget*参数,但实际上传递了一个指向Button对象的指针。对象的静态类型是Widget(如果编译器只查看参数类型),但是它的动态类型是Button。如果调用widget->draw(),其中drawvirtual函数,它将看到动态类型是Button,并确保调用Button::draw

但是,我一般不建议使用原始指针,所以如果可以的话,最好使用引用(Widget&)或智能指针。

下面是一个例子:

struct base { virtual void do_stuff(); = 0 };
struct specialization1: base {
    void do_stuff() override { std::cout << "doing concrete stuff"; }
};

假设你的客户端代码想要调用do_stuff。

第一个实现(这是的方式):

void client_do_stuff( specialization1& s ) { s.do_stuff(); }

此函数有效。如果您决定(从现在起四个月后)添加代码库:

struct specialization2: base {
    void do_stuff() override { std::cout << "doing other concrete stuff"; }
};

您可能希望为specialization2的实例调用void client_do_stuff。您可以使用专门化引用来复制client_do_stuff,但这是代码复制,并且没有必要。

一个更好的解决方案是将client_do_stuff更改为对基类的引用,并对两个特化使用相同的实现。

第二实施:

void client_do_stuff( base& b ) { b.do_stuff(); }
客户机代码:

specialization1 s1;
specialization2 s2;
client_do_stuff(s1); // works
client_do_stuff(s2); // works

client_do_stuff的实现是根据基类的公共接口实现的,而不是专门化。这使得该函数"面向未来"(该原则有时被称为"面向接口编程,而不是面向实现")。

其思想如下:对象具有以下接口(纯虚类)。我将给你的代码提供一个具体的对象,它遵循这个接口,但是该对象的内部细节我将保留给自己(封装)。因此,你的代码不能对对象的精确大小等做任何假设。因此,在编译代码时,必须在操作对象时使用指针或引用。